RFC-0052:類型別名和新類型

RFC-0052:類型別名和新類型
狀態已接受
區域
  • FIDL
說明

本提案旨在正式改進 FIDL 的類型別名和新的型別宣告機制,以及對繫結的影響。更具體來說,這會針對類型別名文法修正、將型別別名呈現在繫結方面,以及稱為「新型別」的新功能。

作者
提交日期 (年/月)2020-01-07
審查日期 (年/月)2020-01-23

「其他名字打電話給我」

摘要

RFC-0019 的部分會在 FIDL 中導入類型別名的概念。本提案旨在正式改進 FIDL 的類型別名和新的類型宣告機制,以及它們對繫結的影響。更具體而言,這會針對類型別名文法修正、將類型別名導入繫結,以及稱為「新類型」的新功能。

提振精神

目前 FIDL 中已有類型別名的功能,而這個 RFC 旨在擴大其範圍和實用性。使用類型別名時,宣告會對繫結造成影響,因此對於 API 名稱轉換作業很有幫助。

除了輸入別名外,這個 RFC 也導入了姊妹概念,也就是新的類型宣告。新類型與別名非常類似,是在本機範圍內宣告新名稱,此名稱包裝另一個類型,但會被視為類型檢查工具 (已命名的類型語意) 不同。這樣一來,FIDL 作者就能更有效地運用其繫結語言的類型系統。

設計

類型別名

如需類型別名的完整背景資訊,RFC-0019 (建議使用樣式別名提供目前) 會醒目顯示原始設計、動機和優先順序。這個 RFC 的目標是鋪滿路徑,以在語言繫結中公開類型別名,並修正 RFC-0019 中的部分語言文法決策。與使用別名類似,新的類型別名純粹是常規,不會影響 ABI。本節主要會概略說明原始功能的變更和改善項目。

文法

alias-declaration = ( attribute-list ) , "alias" , IDENTIFIER , "=" , type-constructor;

type-constructor 應解析為格式完整且存在於目前範圍內的類型參照。換句話說,如果是 vector 這類類型,類型必須包含內部類型和大小限制。

舉例來說,這個值會是有效的:

alias SmallBytes = vector<uint8>:SMALL_NUM;

但這並非 (在等號右側進行部分型別參考):

alias SmallVec = vector:SMALL_NUM;

雖然目前 FIDL 支援使用 using 別名的部分類型參照 (如 SmallVec 範例所示),但系統將移除這項功能。淘汰這項機制的理由有三大:

  1. 您尚未針對 FIDL 完整定義或審查類型泛型。日後,一旦存在類型泛型,語言將能以更正式、更正式的方式描述可參數化的類型和類型別名。
  2. FIDL 語言尚未正式決定,一般人如何將泛型翻譯成不支援泛型的語言 (例如 Go 1、C)。
  3. 目前的語法會建立各種情境,其中可能「間接」需要建立類型參數,才能建立完整格式。此外,我們無法剖析如何剖析巢狀部分類型別名,並將這類別名參數化 (即 using SmallVecOfVecs = vector<vector:SMALL_NUM>:SMALL_NUM)。

系統不支援 protocol 的別名,因為 protocol 無法完整分類為 FIDL 或繫結語言中的類型。目標是將來是否與通訊協定更類似 (即 client_end:ProtocolNameserver_end:ProtocolName) 一樣,重新審視這項決定。

範例

語言 程式碼
FIDL alias SmallBytes = vector<uint8>:SMALL_NUM;
C (簡單的 C 繫結中不存在向量)
typedef small_bytes uint8_t*;
C++ using SmallBytes = std::vector<uint8_t>;
Rust type SmallBytes = Vec<u8>;
飛鏢 2 typedef SmallBytes = List<int>;
查看 type SmallBytes = []uint8;

新類型

新類型可納入基礎類型,但會視為與語言類型系統的基礎類型不同。如果兩個值具有相同的類型大小和形狀 (可能具有特性),但有不同的「語意」意義,這項功能就非常實用。舉例來說,zx_vaddr_tzx_paddr_t 是以相同的基礎 uintptr_t 類型表示,但含義不同 (但很容易取得) 的含義。

新類型可讓您在類型系統中表示這些語意差異。在強型語言中,這意味著在編譯期間透過類型檢查和/或靜態分析找出邏輯錯誤。

電線格式並無任何變化,也不必支付任何費用。

文法

newtype-declaration = ( attribute-list ), "type", IDENTIFIER, "=", type-constructor;

之所以選擇這個語法,是為了配合 RFC-0050:語法修訂

由於新類型應轉換為繫結中的具體類型,因此 type-constructor 應解析為完整格式的類型參照。如下所示:

type MyBytes = vector<uint8>:MAX;

系統不會支援新的 protocol 類型,因為 protocol 無法完整分類為 FIDL 或繫結語言中的類型。此外,對於產生的繫結中 protocol 衍生實體的已命名類型語意目前沒有吸引人的用途。

類型特徵和運算子

如果沒有新的類型,在 FIDL 中使用新的單一欄位 struct 類型,大致可以完成新類型的效果。例如:

struct MyBytes {
    vector<uint8>:MAX value;
};

不過,新類型會指示後端並繫結較小的語意空間,並允許他們產生語言專屬功能,以便對應至原生語言功能,或讓新類型符合人體工學使用。例如:

語言 說明
C 很抱歉,C 的型別系統沒有良好的呈現方式。備用至 typedef
C++ 新類型可轉譯為 class,以公開基礎類型提供的下列功能:(1) 明確的轉換建構函式、(2) 明確指定運算子、(3) 算術與位元運算,以及 (4) 其他運算子 ([]*-> 等)。
Rust 新類型可轉譯為衍生特徵的單例結構,例如基礎類型實作的 From<UnderlyingType>Into<UnderlyingType>HashPartialEqCopyClonestd::ops::* 等。
飛鏢 從與 Dart 團隊對話中,目前無法將新的類型語意對應至語言,但已討論相關用途 3
新類型可降級為類型別名,待執行 #65 [^2] 後。
查看 新類型會直接對應至類型定義。
type <NewType> <UnderlyingType>

基礎類型的整合功能集稱為「類型特徵」 (一種語言尚未正式定義的概念)。

除非在 FIDL 中定義類型特徵,否則新類型預設會繼承基礎類型的特徵。這樣一來,新類型仍能照常使用,與基礎類型相同,無需解除基礎類型,而且可能會復原新類型的類型安全優勢。.不過根據預設,特徵繼承會讓遷移路徑朝選擇採用繼承,帶來大規模的破壞性變更。

範例

C++

// This example uses C++20 concepts for readability but this can be translated to a
// template approach in C++14.
template<typename T>
concept Addable = requires(T a) {
  { a + a } -> T;
};

template<typename T>
concept Multipliable = requires(T a) {
  { a * a } -> T;
};

template <typename T>
concept Copyable = std::is_copy_constructible_v<T> || std::is_copy_assignable_v<T>;

template <typename T>
concept Movable = std::is_move_constructible_v<T> || std::is_move_assignable_v<T>;

class MyNumber {
 public:
  using UnderlyingType = uint32_t;

  explicit MyNumber() = default;

  explicit MyNumber(const UnderlyingType& value)
      requires Copyable<UnderlyingType>
    : value_(value) {}

  explicit MyNumber(UnderlyingType&& value) requires Movable<UnderlyingType>
    : value_(std::move(value)) {}

  MyNumber& operator=(const MyNumber&) = default;
  MyNumber& operator=(MyNumber&&) = default;

  [[nodiscard]] MyNumber operator+(const MyNumber& other) const
      requires Addable<UnderlyingType> && Copyable<UnderlyingType> {
    return MyNumber(value_ + other.value_);
  }

  [[nodiscard]] MyNumber operator+(MyNumber&& other)
      requires Addable<UnderlyingType> && Movable<UnderlyingType> {
    return MyNumber(value_ + other.value_);
  }

  [[nodiscard]] MyNumber operator*(const MyNumber& other) const
      requires Multipliable<UnderlyingType> && Copyable<UnderlyingType> {
    return MyNumber(value_ * other.value_);
  }

  [[nodiscard]] MyNumber operator*(MyNumber&& other)
      requires Multipliable<UnderlyingType> {
    return MyNumber(value_ + other.value_);
  }

  // ...other operators defined here...

  [[nodiscard]] UnderlyingType value() const
      requires Copyable<UnderlyingType> {
    return value_;
  }

  UnderlyingType take_value() requires Movable<UnderlyingType> {
    return std::move(value_);
  }

 private:
  UnderlyingType value_;
};

Rust

#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
struct MyNumber(u32);

impl From<u32> for MyNumber {
    fn from(value: u32) -> Self {
        MyNumber(value)
    }
}

impl Into<u32> for MyNumber {
    fn into(self) -> u32 {
        self.0
    }
}

impl Add for MyNumber {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Self(self.0 + rhs.0)
    }
}

impl Mul for MyNumber {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self::Output {
        Self(self.0 * rhs.0)
    }
}

// ...implement other traits here...

  1. 不久後, Go將會出現泛型

  2. Pending Dart 推出非函式類型的 typedefs (#65)

  3. 請查看 Dart 問題 #42 中的註解。