RFC-0052:类型别名和新类型

RFC-0052:类型别名和新类型
状态已接受
领域
  • FIDL
说明

此提案旨在正式改进 FIDL 的类型别名和新的类型声明机制及其对绑定的影响。更具体地说,这对类型别名语法、向绑定公开类型别名以及一项称为新类型的新功能提出了建议。

作者
提交日期(年-月-日)2020-01-07
审核日期(年-月-日)2020-01-23

“用其他名字给我打电话”

摘要

部分 RFC-0019 在 FIDL 中引入了类型别名的概念。 此提案旨在正式改进 FIDL 的类型别名和新类型 声明机制及其对绑定的影响。更具体地说, 对类型别名语法、类型别名 以及一项称为“新类型”的新功能

设计初衷

类型别名现已成为 FIDL 中的一项功能,此 RFC 旨在扩展 范围和实用性使用类型别名时,声明将产生影响 因此有助于 API 名称转换。

除了类型别名,此 RFC 还引入了姊妹概念 - 新的 类型声明。新类型(类似于别名)会在本地 该作用域封装了另一种类型,但被视为不同于类型检查工具 (命名类型语义)。这将使 FIDL 作者能够更好地利用 其绑定语言的类型系统。

设计

类型别名

如需了解类型别名的完整上下文, RFC-0019 (使用样式别名建议当前的样式)突出显示原始设计, 以及优先顺序。此 RFC 旨在为公开披露声明的 输入语言绑定中的别名并优化一些语言语法 决策。与使用别名非常相似,新类型别名纯粹 是标称的,不会影响 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. 当前语法会造成类型参数可能 创建完全成型的类型时为隐式类型。此外,这些 API 的 如何解析和参数化嵌套的部分类型别名(即 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 衍生的命名类型语义,目前没有令人信服的用例 实体。

类型 trait 和运算符

如果没有新类型,只需使用 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 中定义了类型特征,否则新类型应继承 即底层类型的 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...

  1. 在不久的将来,Go 将会推出泛型

  2. 等待 Dart 引入 适用于非函数类型的 typedefs (#65)

  3. 查看关于 Dart 问题的评论 #42