| RFC-0052:类型别名和新类型 | |
|---|---|
| 状态 | 已接受 |
| 适用范围 |
|
| 说明 | 此提案旨在正式发展 FIDL 的类型别名和新类型声明机制及其对绑定的影响。更具体地说,此提案建议改进类型别名语法、向绑定公开类型别名,以及引入一项名为“新类型”的新功能。 |
| 作者 | |
| 提交日期(年-月-日) | 2020-01-07 |
| 审核日期(年-月-日) | 2020-01-23 |
“请叫我另一个名字”
摘要
RFC-0019此提案旨在正式发展 FIDL 的类型别名和新类型声明机制及其对绑定的影响。更具体地说,此提案建议改进类型别名语法、向绑定公开类型别名,以及引入一项名为“新类型”的新功能。
设计初衷
类型别名目前已是 FIDL 中的一项功能,此 RFC 旨在扩大其范围和实用性。借助类型别名,声明将对绑定产生影响,因此有助于 API 名称转换。
除了类型别名之外,此 RFC 还引入了一个姊妹概念 - 新类型声明。与别名非常相似,新类型在本地范围内声明一个新名称,该名称封装了另一种类型,但类型检查器会将其视为不同的类型(命名类型语义)。这样,FIDL 作者就可以更好地利用其绑定语言的类型系统。
设计
类型别名
如需了解类型别名的完整背景信息,请参阅 RFC-0019 (其中提出了当前使用样式的别名),该 RFC 重点介绍了原始设计、 设计初衷和优先级。此 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 示例所示),但此功能将被移除。弃用此功能的原因有三:
- 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>; |
| Rust | type SmallBytes = Vec<u8>; |
| Dart 2 | typedef SmallBytes = List<int>; |
| Go | type SmallBytes = []uint8; |
新类型
新类型是一种封装了底层类型的类型,但在语言的类型系统中,它被视为与底层类型不同。如果两个值具有相同的类型大小和形状(以及可能的特征),但具有不同的
语义含义,则此功能可能很有用。例如,zx_vaddr_t 和 zx_paddr_t 由相同的底层 uintptr_t
类型表示,但具有不同的(但很容易混淆的)含义。
新类型允许在类型系统中表达这些语义差异。在强类型语言中,这意味着可以在编译时通过类型检查和/或静态分析来捕获逻辑 bug。
这不会更改或增加线格式的成本。
语法
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,该 class 可能会公开底层类型的以下功能:(1) 显式转换构造函数,(2) 显式赋值运算符,(3) 算术和按位运算,以及 (4) 其他运算符([]、*、-> 等) |
| Rust | 新类型可以转换为单例结构体,该结构体派生出底层类型实现的特征,例如 From<UnderlyingType>、Into<UnderlyingType>、Hash、PartialEq、Copy、Clone、std::ops::* 等。 |
| Dart | 根据与 Dart 团队的对话,目前无法将新类型语义映射到该语言,不过已经讨论过支持此类用例 3。 新类型可以降级为类型别名,具体取决于 #65 [^2]。 |
| Go | 新类型直接映射到类型定义。type <NewType> <UnderlyingType> |
底层类型的一组固有功能在下文中称为“类型特征”(该概念尚未在语言中正式定义)。
除非/直到在 FIDL 中定义类型特征,否则新类型应默认继承底层类型的特征。这样做后,新类型仍将以与底层类型相同的方式发挥作用,而无需解封装底层类型,并可能撤消新类型的类型安全优势。未来,能够定义新类型的自定义 trait 行为可能很有用,并且此设计不会阻止未来朝着此方向发展。不过,默认情况下,特征继承会使迁移路径朝着选择性继承特征的方向发生重大变化。
示例
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...
-
Go 将在不久的将来提供泛型。 ↩
-
具体取决于 Dart 何时为非函数类型引入 typedef (#65)。 ↩