Rust 绑定

生成 FIDL Rust crate

您可以通过以下两种方式从 FIDL 库生成 FIDL Rust crate:

  1. 使用标准 FIDL 工具链手动创建。
  2. 使用 Fuchsia 构建系统自动进行(在后台使用标准 FIDL 工具链)。此选项仅在 Fuchsia 源代码树中提供。

FIDL library 映射到名为 fidl_ 的 Rust 库 crate,后跟用下划线(而不是点)的完整库路径。

例如,假设存在 library 声明:

library fuchsia.examples;

对应的 FIDL crate 名为 fidl_fuchsia_examples

use fidl_fuchsia_examples as fex;

特征

FIDL Rust crate 中的某些方法和常量是通过实现 FIDL 运行时 crate 中的特征提供的。如需访问它们,您必须导入相应的特征。最简单的方法是从 prelude 模块一次性导入所有内容:

use fidl::prelude::*;

前言部分会使用 as _ 语法重新导出特征,因此 glob 导入只会将特征纳入解析方法和常量的范围。它不会导入特征名称本身。

常量

在给定常量的情况下:

const BOARD_SIZE uint8 = 9;
const NAME string = "Tic-Tac-Toe";

FIDL 工具链生成以下常量:

  • pub const BOARD_SIZE: u8
  • pub const NAME: &str

内置类型中概述了 FIDL 基元类型与 Rust 类型之间的对应关系。

字段

本部分将介绍 FIDL 工具链如何将 FIDL 类型转换为 Rust 中的原生类型。这些类型可以显示为聚合类型的成员或协议方法的参数。

内置类型

在下表中,当同时指定了“拥有”和“借用”变体时,“拥有”类型是指将出现在聚合类型中的类型(例如,结构体字段或矢量元素的类型),“借用”类型是指(从响应服务器的角度)用作协议方法参数时显示的类型。为了利用 Rust 的所有权模型,我们需要区分拥有和借用的所有权。当使用 T 类型的参数发出请求时,代理函数调用不需要获取 T 的所有权,因此 FIDL 工具链需要生成借用的 T 版本。借用的版本通常使用 &mut,因为类型 T 可能包含句柄,在这种情况下,FIDL 绑定会在编码(修改输入)时清零句柄。如果输入值不包含句柄,则使用 &mut 而不是获得所有权,调用方可以重复使用输入值。

FIDL 类型 Rust 类型
bool bool
int8 i8
int16 i16
int32 i32
int64 i64
uint8 u8
uint16 u16
uint32 u32
uint64 u64
float32 f32
float64 f64
array<T, N> &mut [T; N] (已借用)
[T, N] (自有)
vector<T>:N &[T](借用,当 T 为数字基元时)
&mut dyn ExactSizeIterator(借用)
Vec (自有)
string &str (已借用)
String (自有)
server_end:P fidl::endpoints::ServerEnd<PMarker>,其中 PMarker 是此协议的标记类型
client_end:P fidl::endpoints::ClientEnd<PMarker> 其中 PMarker 是此协议的标记类型
zx.Handle fidl::Handle
zx.Handle:S 系统会使用相应的标识名类型。例如 fidl::Channelfidl::Vmo

用户定义的类型

位、枚举和表始终使用其生成的类型 T 进行引用。结构体和联合可以是必需或可选的,并且可以在有主上下文或借用上下文中使用,这意味着可能有四种等效的 Rust 类型。对于给定的 struct Tunion T,类型如下:

拥有 借用的
必需 T &mut T
可选 Option<T> Option<&mut T>

请求、响应和事件参数

当 FIDL 需要生成单个 Rust 类型来表示请求、响应或事件的参数(例如结果类型)时,它会使用以下规则:

  • 多个参数表示为参数类型的元组。
  • 单个参数仅使用参数的类型表示。
  • 一组空参数使用单位类型 () 表示。

类型

钻头

根据 bits 的定义:

type FileMode = strict bits : uint16 {
    READ = 0b001;
    WRITE = 0b010;
    EXECUTE = 0b100;
};

FIDL 工具链生成一组名为 FileModebitflags,其中包含 FileMode::READFileMode::WRITEFileMode::EXECUTE 标志。

bitflags 结构体还提供以下方法:

  • get_unknown_bits(&self) -> u16:返回一个基元值,其中仅包含此位值中的未知成员。对于严格位,会被标记为 #[deprecated] 并始终返回 0。
  • has_unknown_bits(&self) -> bool:返回此值是否包含任何未知位。对于严格位,标记为 #[deprecated] 并始终返回 false

生成的 FileMode 结构体始终具有一组完整的 #[derive] 规则

用法示例:

let flags = fex::FileMode::READ | fex::FileMode::WRITE;
println!("{:?}", flags);

枚举

根据 enum 定义:

type LocationType = strict enum {
    MUSEUM = 1;
    AIRPORT = 2;
    RESTAURANT = 3;
};

FIDL 工具链会使用指定的底层类型生成 Rust enum,如果未指定,则生成 u32

#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(u32)]
pub enum LocationType {
    Museum = 1,
    Airport = 2,
    Restaurant = 3,
}

使用以下方法:

  • from_primitive(prim: u32) -> Option<Self>:返回与判别值对应的枚举变体的 Some,否则返回 None
  • into_primitive(&self) -> u32:返回基础判别值。
  • is_unknown(&self) -> bool:返回此枚举是否未知的指示值。对于严格类型,此标记会被标记为 #[deprecated] 并始终返回 false

如果 LocationType灵活的,则还将使用以下其他方法:

  • from_primitive_allow_unknown(prim: u32) -> Self:基于原始值创建枚举的实例。
  • unknown() -> Self:返回一个占位符未知枚举值。如果枚举包含标有 [Unknown] 的成员,则此方法返回的值包含指定未知成员的值。

生成的 LocationType enum 始终具有一整套 #[derive] 规则

用法示例:

let from_raw = fex::LocationType::from_primitive(1).expect("Could not create LocationType");
assert_eq!(from_raw, fex::LocationType::Museum);
assert_eq!(fex::LocationType::Restaurant.into_primitive(), 3);

为了提供源代码兼容性,柔性环境枚举具有未知宏,应该用于匹配未知成员,而不是 _ 模式。有关示例,请参阅 LocationTypeUnknown!() 宏的用法:

match location_type {
    fex::LocationType::Museum => println!("museum"),
    fex::LocationType::Airport => println!("airport"),
    fex::LocationType::Restaurant => println!("restaurant"),
    fex::LocationTypeUnknown!() => {
        println!("unknown value: {}", location_type.into_primitive())
    }
}

未知宏的作用与 _ 模式相同,但可以配置为扩展为详尽的匹配。这对于发现缺失的案例非常有用。

结构体

在给定 struct 声明的情况下:

type Color = struct {
    id uint32;
    @allow_deprecated_struct_defaults
    name string:MAX_STRING_LENGTH = "red";
};

FIDL 工具链生成 Rust struct

#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Color {
    pub id: u32,
    pub name: String,
}

生成的 Color struct 遵循 #[derive] 规则

用法示例:

let red = fex::Color { id: 0u32, name: "red".to_string() };
println!("{:?}", red);

联合体

根据 union 的定义:

type JsonValue = strict union {
    1: reserved;
    2: int_value int32;
    3: string_value string:MAX_STRING_LENGTH;
};

FIDL 工具链生成 Rust enum

#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum JsonValue {
    IntValue(i32),
    StringValue(String),
}

使用以下方法:

  • ordinal(&self) -> u64:返回有效变体的序数,如 FIDL 类型中所指定。
  • is_unknown(&self) -> bool:返回此联合是否未知。对于非灵活联合类型,始终返回 false。对于严格类型,该参数被标记为 #[deprecated] 并始终返回 false

如果 JsonValue灵活的,则还将使用以下其他方法:

  • unknown_variant_for_testing() -> Self:创建未知联合值。这只能用于测试。

生成的 JsonValue enum 遵循 #[derive] 规则

用法示例:

let int_val = fex::JsonValue::IntValue(1);
let str_val = fex::JsonValue::StringValue("1".to_string());
println!("{:?}", int_val);
assert_ne!(int_val, str_val);

灵活联合和未知变体

灵活联合会生成一个额外的变体来表示未知情况。此变体被视为私有,不应直接引用。

为了提供源代码兼容性,灵活联合使用一个未知宏,应该用于匹配未知成员,而不是 _ 模式。有关示例,请参阅 JsonValueUnknown!() 宏的用法:

match json_value {
    fex::JsonValue::IntValue(val) => println!("int: {}", val),
    fex::JsonValue::StringValue(val) => println!("string: {}", val),
    fex::JsonValueUnknown!() => println!("unknown ordinal: {}", json_value.ordinal()),
}

未知宏的作用与 _ 模式相同,但可以配置为扩展为详尽的匹配。这对于发现缺失的案例非常有用。

当包含与未知变体的并集的 FIDL 消息解码为 JsonValue 时,JsonValue::is_unknown() 返回 true。

灵活联合具有自定义 PartialEq,使未知变体的行为类似于 NaN:它们的比较并不等于任何内容,包括它们本身。请参阅 RFC-0137: 舍弃 FIDL 中的未知数据,了解有关此决定的背景信息。

解码未知变体时,严格联合会失败。 灵活联合会在解码未知变体时成功,但在重新编码时失败。

表格

根据表定义:

type User = table {
    1: reserved;
    2: age uint8;
    3: name string:MAX_STRING_LENGTH;
};

FIDL 工具链生成包含可选成员的 struct User

#[derive(Debug, PartialEq)]
pub struct User {
  pub age: Option<u8>,
  pub name: Option<String>,
  #[doc(hidden)]
  pub __source_breaking: fidl::marker::SourceBreaking,
}

如果在解码期间遇到任何未知字段,则这些字段会被舍弃。但既无法访问这些数据,也无法确定它们是否发生了。

__source_breaking 成员会表明,在添加新字段时,详尽初始化表会导致 API 中断。您应改用结构体更新语法,使用 Default::default() 填充未指定的字段。例如:

let user = fex::User { age: Some(20), ..Default::default() };
println!("{:?}", user);
assert!(user.age.is_some());

同样,表也不允许详尽匹配。相反,您必须使用 .. 语法忽略未指定的字段。例如:

let fex::User { age, name, .. } = user;

生成的 User struct 遵循 #[derive] 规则

内嵌布局

生成的 Rust 代码会将 fidlc 预留的名称用于内嵌布局。

派生

当 FIDL 工具链为 FIDL 类型生成新的 structenum 时,它会尝试从预定义的有用特征列表中尽可能多地 derive 特征,包括 DebugCopyClone 等。完整的特征列表请参阅附录 A

对于聚合类型(例如结构体、联合和表),派生集的确定方式是从所有可能的派生列表开始,然后根据类型中以传递方式存在的字段移除一些派生类型。例如,以传递方式包含 vector 的聚合类型不会派生 Copy,包含 handle 的类型(即未标记为 resource 的类型)不会派生 CopyClone。如有疑问,请参阅生成的代码,以检查特定类型衍生了哪些特征。如需了解实现详情,请参阅附录 B

协议

假设有一个 protocol

closed protocol TicTacToe {
    strict StartGame(struct {
        start_first bool;
    });
    strict MakeMove(struct {
        row uint8;
        col uint8;
    }) -> (struct {
        success bool;
        new_state box<GameState>;
    });
    strict -> OnOpponentMove(struct {
        new_state GameState;
    });
};

TicTacToe 交互的主要入口点是 TicTacToeMarker 结构体,它包含两个关联的类型:

  • Proxy:用于异步客户端的关联代理类型。在此示例中,这是生成的 TicTacToeProxy 类型。同步客户端应直接使用 TicTacToeSynchronousProxy(请参阅同步),它不会存储在 TicTacToeMarker 上的关联类型中。
  • RequestStream:实现此协议的服务器需要处理的关联请求流。在本示例中,这是由 FIDL 生成的 TicTacToeRequestStream

此外,TicTacToeMarker 还会通过实现 fidl::endpoints::ProtocolMarker 获得以下关联常量:

  • DEBUG_NAME: &’static str:适合用于调试的服务的名称。

系统可能会根据应用于协议或其方法的协议和方法属性生成其他代码。

客户端

异步

对于异步客户端,FIDL 工具链会生成一个包含以下内容的 TicTacToeProxy 结构体:

关联的类型:

  • TicTacToeProxy::MakeMoveResponseFut:双向方法响应的 Future 类型。此类型会实现 std::future::Future<Output = Result<(bool, Option<Box<GameState>>), fidl::Error>> + Send

方法:

  • new(channel: fidl::AsyncChannel) -> TicTacToeProxy:为 TicTacToe 创建新的代理。
  • take_event_stream(&self) -> TicTacToeEventStream:从服务器端获取事件的 Stream(请参阅事件)。

实现 fidl::endpoints::Proxy 的方法:

  • from_channel(channel: fidl::AsyncChannel) -> TicTacToeProxy:与 TicTacToeProxy::new 相同。
  • into_channel(self) -> Result<fidl::AsyncChannel>:尝试将代理转换回通道。
  • as_channel(&self) -> &fidl::AsyncChannel:获取对代理底层渠道的引用
  • is_closed(&self) -> bool:检查代理是否已收到 PEER_CLOSED 信号。
  • on_closed<'a>(&'a self) -> fuchsia_async::OnSignals<'a>:获取在代理收到 PEER_CLOSED 信号时完成的 Future。

实现 TicTacToeProxyInterface 的方法:

  • start_game(&self, mut start_first: bool) -> Result<(), fidl::Error>:触发和忘记协议方法的代理方法。它接受请求参数作为参数,并返回空结果。
  • make_move(&self, mut row: u8, mut col: u8) -> Self::MakeMoveResponseFut:双向方法的代理方法。它接受请求参数作为参数,并返回响应的 Future

Rust 教程中提供了一个设置异步代理的示例。

TicTacToeProxyInterface trait 对于测试客户端代码非常有用。例如,如果您编写了一个接受 &T 作为参数的函数,其中 T: TicTacToeProxyInterface,则可以使用虚构代理类型对该函数进行单元测试:

use futures::future::{ready, Ready};

struct FakeTicTacToeProxy {
    move_response: (bool, Option<Box<GameState>>),
}

impl TicTacToeProxyInterface for FakeTicTacToeProxy {
    fn start_game(&self, mut start_first: bool) -> Result<(), fidl::Error> {
      Ok(())
    }

    type MakeMoveResponseFut = Ready<fidl::Result<(bool, Option<Box<GameState>>)>>;
    fn make_move(&self, mut row: u8, mut col: u8) -> Self::MakeMoveResponseFut {
        ready(self.move_response.clone())
    }
}

同步

对于 TicTacToe 协议的同步客户端,FIDL 工具链会使用以下方法生成 TicTacToeSynchronousProxy 结构:

  • new(channel: fidl::Channel) -> TicTacToeSynchronousProxy:在通道的客户端端返回一个新的同步代理。假定服务器端实现了 TicTacToe 协议。
  • into_channel(self) -> fidl::Channel:将代理转换回通道。
  • start_game(&self, mut a: i64) -> Result<(), fidl::Error>:即触发即忘记的方法的代理方法:它会将请求参数作为参数并返回空的结果。
  • make_move(&self, mut row: u8, mut col: u8, __deadline: zx::Time) -> Result<(bool, Option<Box<GameState>>), fidl::Error>:双向方法的代理方法。它接受请求参数作为参数,后跟一个截止时间参数,该截止时间参数指示方法调用等待响应的时长(或者无限期等待 zx::Time::INFINITE 的时间)。它会返回响应参数Result
  • wait_for_event(&self, deadline: zx::Time) -> Result<TicTacToeEvent, fidl::Error>:在收到事件或截止日期到期之前屏蔽(使用 zx::Time::INFINITE 无限期屏蔽)。它会返回 TicTacToeEvent 枚举Result

Rust 教程中提供了一个设置同步代理的示例。

服务器

协议请求流

为了表示发送到服务器的传入请求流,FIDL 工具链会生成一个 TicTacToeRequestStream 类型来实现 futures::Stream<Item = Result<TicTacToeRequest, fidl::Error>> 以及 fidl::endpoints::RequestStream。每个协议都有一个相应的请求流类型。

请求枚举

TicTacToeRequest 是一个枚举,表示 TicTacToe 协议的可能请求。它具有以下变体:

  • StartGame { start_first: bool, control_handle: TicTacToeControlHandle }:触发和忘记请求,其中包含请求参数和控制句柄
  • MakeMove { row: u8, col: u8, responder: TicTacToeMakeMoveResponder }:双向方法请求,包含请求参数和响应程序

系统会为每个协议生成一个这样的枚举。

请求回复者

每种双向方法都有一个对应的生成的响应程序类型,服务器使用该类型来响应请求。在此示例中,FIDL 工具链只有一种双向方法,因此会生成 TicTacToeMakeMoveResponder,该方法会提供以下方法:

  • send(self, mut success: bool, mut new_state: Option<&mut GameState>) -> Result<(), fidl::Error>:发送响应。
  • send_no_shutdown_on_err(self, mut success: bool, mut new_state: Option<&mut GameState>) -> Result<(), fidl::Error>:与 send 类似,但在发生错误时不会关闭渠道。
  • control_handle(&self) -> &TicTacToeControlHandle:获取底层控制句柄
  • drop_without_shutdown(mut self):舍弃响应者,而不关闭通道。

协议控制句柄

FIDL 工具链生成 TicTacToeControlHandle 以在服务器端封装 TicTacToe 协议的客户端端点。它包含以下方法:

  • shutdown(&self):关闭渠道。
  • shutdown_with_epitaph(&self, status: zx_status::Status):发送警示,然后关闭频道。
  • send_on_opponent_move(&self, mut new_state: &mut GameState) -> Result<(), fidl::Error>:事件的代理方法,该方法接受事件的参数作为参数,并返回空结果(请参阅事件)。

事件

客户端

为了在异步客户端上接收事件,FIDL 工具链会生成一个 TicTacToeEventStream,您可以使用 TicTacToeProxy 上的 take_event_stream() 方法获取该 TicTacToeEventStreamTicTacToeEventStream 会实现 futures::Stream<Item = Result<TicTacToeEvent, fidl::Error>>

为了在同步客户端上接收事件,FIDL 工具链会在 TicTacToeSynchronousProxy 上生成一个返回 TicTacToeEventwait_for_event 方法。

TicTacToeEvent 是表示可能事件的枚举。它具有以下变体:

  • OnOpponentMove { new_state: GameState }TicTacToeEvent 事件的判别式。

并提供以下方法:

  • into_on_opponent_move(self) -> Option<GameState>:返回事件参数Some,如果变体与方法调用不匹配,则返回 None

服务器

服务器可以使用与协议对应的控制句柄来发送事件。控制句柄可通过从客户端接收的 TicTacToeRequest 获取。对于触发和忘记方法,可通过 control_handle 字段使用控制句柄;对于双向方法,可通过响应程序上的 control_handle() 方法使用。此外,协议的控制句柄也可以通过相应的请求流(在此示例中为 TicTacToeRequestStream)获取,因为它实现了 fidl::endpoints::RequestStream

成果

对于包含错误类型的方法:

protocol TicTacToe {
    MakeMove(struct {
      row uint8;
      col uint8;
    }) -> (struct {
      new_state GameState;
    }) error MoveError;
};

FIDL 工具链为 std::result::Result<GameState, MoveError> 生成公共 TicTacToeMakeMoveResult 类型别名。系统会生成此方法的其余绑定代码,就像它具有一个类型为 TicTacToeMakeMoveResult 的响应参数 result 一样。用于成功结果的类型遵循参数类型转换规则

未知互动处理

服务器端

当某个协议声明为 openajar 时,生成的请求枚举将具有一个名为 _UnknownMethod 的附加变体,该变体具有以下字段:

#[non_exhausitve]
_UnknownMethod {
    /// Ordinal of the method that was called.
    ordinal: u64,
    /// Control handle for the protocol.
    control_handle: TicTacToeControlHandle,
    /// Enum indicating whether the method is a one-way method or a two way
    /// method. This field only exists if the protocol is open.
    method_type: fidl::MethodType,
}

MethodType 是包含两个单元变体(OneWayTwoWay)的枚举,用于告知调用哪类方法。

每当服务器收到灵活的未知事件时,请求流都会发出请求枚举的这个变体。

客户端

客户端无法判断服务器是否知道 flexible 单向方法。对于 flexible 双向方法,如果服务器不知道该方法,则客户端将收到值为 fidl::Error::UnsupportedMethodErr 结果。只有灵活的双向方法才会出现 UnsupportedMethod 错误。

除了可能会出现 UnsupportedMethod 错误之外,客户端上的 strictflexible 方法之间没有 API 差异。

对于 openajar 协议,生成的事件枚举将具有一个名为 _UnknownEvent 的附加变体,该变体具有以下字段:

#[non_exhaustive]
_UnknownEvent {
    /// Ordinal of the event that was sent.
    ordinal: u64,
}

每当客户端收到未知事件时,客户端事件流都会发出事件枚举的这一变体。

协议构成

FIDL 没有继承的概念,它会针对所有组合协议生成完整代码(如上所述)。换句话说,为以下内容生成的代码:

protocol A {
    Foo();
};

protocol B {
    compose A;
    Bar();
};

与以下代码相同:

protocol A {
    Foo();
};

protocol B {
    Foo();
    Bar();
};

协议和方法属性

过渡风格

@transitional 属性仅影响 ProxyInterface 特征,有时测试代码会使用此特征。对于非测试代码,可以通过让请求处理程序暂时在 Request 处理程序中使用无限别名匹配分支,在服务器端对协议进行转换。客户端代码不需要进行软转换,因为生成的代理将始终实现所有方法。

对于带有 @transitional 属性注解的方法,异步客户端ProxyInterface 特征会提供调用 unimplemented!() 的默认实现。如前所述,这对 Proxy 类型没有影响,后者始终会实现所有特征的方法。不过,当 ProxyInterface 特征用于客户端单元测试中的虚构代理时,它对软转换有所帮助。

可检测到

对于带有 @discoverable 属性注解的协议,Marker 类型还会实现 fidl::endpoints::DiscoverableProtocolMarker trait。这提供了与 PROTOCOL_NAME 关联的常量。

显式编码和解码

FIDL 消息发送时会自动编码,在收到消息时会自动对其进行解码。您还可以显式进行编码和解码,例如将 FIDL 数据保存到文件中。根据 RFC-0120:以独立方式使用 FIDL 传输格式,这些绑定提供了一个持久性 API 和一个独立的 API

持久性

建议使用 persistunpersist 来明确编码和解码。这适用于非资源结构体、表和联合。

例如,您可以保留一个 Color 结构体

let original_value = fex::Color { id: 0, name: "red".to_string() };
let bytes = fidl::persist(&original_value)?;

以后再取消保留:

let decoded_value = fidl::unpersist(&bytes)?;
assert_eq!(original_value, decoded_value);

一般界面

对于持久性 API 不足的高级用例,您可以使用独立的编码和解码 API。这适用于所有结构体、表和联合。有两组函数:一组用于类型,另一组用于资源类型。

例如,由于 JsonValue union 是一种值类型,因此我们使用 standalone_encode_value 对其进行编码:

let original_value = fex::JsonValue::StringValue("hello".to_string());
let (bytes, wire_metadata) = fidl::standalone_encode_value(&original_value)?;

这将返回一个字节矢量和一个存储有线格式元数据的不透明对象。如需进行解码,请将这两个值都传递给 standalone_decode_value

let decoded_value = fidl::standalone_decode_value(&bytes, &wire_metadata)?;
assert_eq!(original_value, decoded_value);

再看一个示例,以结构体 EventStruct 为例:

type EventStruct = resource struct {
    event zx.Handle:<EVENT, optional>;
};

由于这是一种资源类型,因此我们使用 standalone_encode_resource 对其进行编码:

let original_value = fex::EventStruct { event: Some(fidl::Event::create()) };
let (bytes, handle_dispositions, wire_metadata) =
    fidl::standalone_encode_resource(original_value)?;

除了字节和传输格式元数据之外,这还会返回 HandleDisposition 的矢量。如需进行解码,请将句柄处置转换为 HandleInfo,然后将这三个值都传递给 standalone_decode_resource

let mut handle_infos = fidl::convert_handle_dispositions_to_infos(handle_dispositions)?;
let decoded_value: fex::EventStruct =
    fidl::standalone_decode_resource(&bytes, &mut handle_infos, &wire_metadata)?;
assert!(decoded_value.event.is_some());

附录 A:衍生特征

"Debug",
"Copy",
"Clone",
"Default",
"Eq",
"PartialEq",
"Ord",
"PartialOrd",
"Hash",

附录 B:填充派生

特征派生规则的计算在 fidlgen_rust 中可见:

// Calculates what traits should be derived for each output type,
// filling in all `*derives` in the IR.
func (c *compiler) fillDerives(ir *Root) {