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

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

這項提案旨在正式演進 FIDL 的型別別名和新型別宣告機制,以及這些機制對繫結的影響。具體來說,這項提案建議對型別別名文法進行改良、將型別別名公開給繫結,以及推出稱為「新類型」的新功能。

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

「Call me by another name」(幫我取個別名)

摘要

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 目前支援部分型別參照 (如SmallVec範例所示),並使用 using 別名,但這項功能將會移除。淘汰這項功能的理由有三:

  1. FIDL 的型別泛型尚未完全定義或審查。在未來有型別泛型的語言中,描述可參數化的型別和型別別名時,會更正式且更完善。
  2. FIDL 語言尚未正式決定如何將泛型轉換為不支援泛型的語言 (例如 Go 1、C)。
  3. 目前的語法會建立情境,導致可能隱含需要型別參數,才能建立完整型別。此外,如何剖析及參數化巢狀偏好類型別名 (即 using SmallVecOfVecs = vector<vector:SMALL_NUM>:SMALL_NUM) 的具體做法也不清楚。

系統不支援 protocol 的別名,因為無論是在 FIDL 或繫結語言中,protocol 都無法完全歸類為型別。目標是日後重新審查這項決定,前提是通訊協定變得更像型別 (即 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,因為無論是在 FIDL 或繫結語言中,protocol 都無法完全分類為某種型別。此外,目前在產生的繫結中,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 中的留言。