| RFC-0052:型別別名和新類型 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 這項提案旨在正式演進 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 別名,但這項功能將會移除。淘汰這項功能的理由有三:
- FIDL 的型別泛型尚未完全定義或審查。在未來有型別泛型的語言中,描述可參數化的型別和型別別名時,會更正式且更完善。
- FIDL 語言尚未正式決定如何將泛型轉換為不支援泛型的語言 (例如 Go 1、C)。
- 目前的語法會建立情境,導致可能隱含需要型別參數,才能建立完整型別。此外,如何剖析及參數化巢狀偏好類型別名 (即
using SmallVecOfVecs = vector<vector:SMALL_NUM>:SMALL_NUM) 的具體做法也不清楚。
系統不支援 protocol 的別名,因為無論是在 FIDL 或繫結語言中,protocol 都無法完全歸類為型別。目標是日後重新審查這項決定,前提是通訊協定變得更像型別 (即 client_end:ProtocolName 和 server_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_t 和 zx_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>、Hash、PartialEq、Copy、Clone、std::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...