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>;
Dart 2 typedef SmallBytes = List<int>;
Go type SmallBytes = []uint8;

新类型

新类型是指封装了底层类型但在语言的类型系统中被视为与底层类型不同的类型。如果两个值具有相同的类型大小和形状(以及可能的特征),但具有不同的语义含义,则此功能可能很有用。例如,zx_vaddr_tzx_paddr_t 由相同的底层 uintptr_t 类型表示,但具有不同的(但容易混淆的)含义。

借助新类型,可以在类型系统中表达这些语义差异。在强类型语言中,这意味着可以在编译时通过类型检查和/或静态分析来捕获逻辑 bug。

这不会改变或增加有线格式的费用。

语法

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 根据与 Dart 团队的对话,目前无法将新的类型语义映射到该语言,不过我们已讨论过支持此类使用情形 3
新的类型可以降级为类型别名,具体取决于 #65 [^2]。
Go 新类型直接映射到类型定义。
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. 等待 Dart 引入非函数类型的 typedef (#65)。 

  3. 请参阅 Dart 的问题 #42 中的评论。