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. 對於泛型如何轉譯為不支援泛型的語言 (例如 Go 1、C),FIDL 語言尚未做出正式決定。
  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>;
荒漠油廠 type SmallBytes = Vec<u8>;
Dart 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) 其他運算子 ([]*-> 等)。
荒漠油廠 新類型可轉譯為單例結構體,從中衍生出屬性,例如基礎類型實作的 From<UnderlyingType>Into<UnderlyingType>HashPartialEqCopyClonestd::ops::* 等。
Dart 根據與 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_;
};

荒漠油廠

#[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. 等待 Dart 推出非函式類型的 typedefs (#65)。 

  3. 請參閱 Dart 問題 #42 的留言。