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 中的 trait。若要访问它们,请按以下步骤操作: 您必须导入相应的 trait。最简单的方法是 从序言模块中一次性导入所有模块:

use fidl::prelude::*;

序言使用 as _ 语法重新导出 trait,因此仅导入 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 类型转换为原生 类型。这些类型可以在汇总类型中显示为成员,也可以显示为 传递给协议方法。

内置类型

在下表中,如果和“已借”的变体后 “拥有的”type 是指在汇总类型中显示的类型(例如, 结构体字段或矢量元素的类型),而“借用”的则是类型引用 与将协议方法参数用作协议方法参数时显示的类型 (从客户端的角度)或响应元组值(从服务器的 )。归根结底是自有的和借用的 充分利用 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 工具链会生成一组 已调用bitflags FileMode,带有 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:返回此枚举是否未知的指示值。对于 strict 类型,标记为 #[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);

为了提供来源兼容性,flexible枚举具有未知的 宏(而不是 _)来匹配未知成员 模式。如需查看示例,请参阅 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: int_value int32;
    2: 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 消息被解码为 JsonValueJsonValue::is_unknown() 会返回 true。

灵活联合体有一个自定义 PartialEq,可让未知变体的行为类似于 NaN:它们并不等于任何对象(包括其自身)。请参阅 RFC-0137: 舍弃 FIDL 中的未知数据,以获取关于此决定的背景信息。

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

表格

根据 table 定义:

type User = table {
    1: age uint8;
    2: 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 时, 尝试从预定义的有用 trait 列表中 derive 的 trait 数量与 它可以包括 DebugCopyClone 等。完整的特征列表 附录 A

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

协议

给定一个协议

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:服务器的相关请求流 实现此协议所需处理的事务。在此示例中, TicTacToeRequestStream:由 FIDL 生成。

此外,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 信号时完成。

实现 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::MonotonicTime) -> Result<(bool, Option<Box<GameState>>), fidl::Error>:适用于 2 的代理方法 Way 方法。该方法将请求形参作为实参,后跟 期限参数,该参数指示方法调用等待 响应(或 zx::MonotonicTime::INFINITE 则无限期屏蔽)。它会返回一个 响应参数Result
  • wait_for_event(&self, deadline: zx::MonotonicTime) -> Result<TicTacToeEvent, fidl::Error>:在收到事件或 截止时间到期(使用 zx::MonotonicTime::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 }:A 触发和遗忘请求,其中包含请求参数和控制句柄
  • MakeMove { row: u8, col: u8, responder: TicTacToeMakeMoveResponder }:A 2 Way 方法请求,其中包含请求参数和 回复者。

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

请求回复者

每种双向方法都有一个生成的响应者类型, 来响应请求。在本示例中,只有一个双向 方法,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,可使用 take_event_stream() 获取 方法(针对 TicTacToeProxy)。 TicTacToeEventStream 会实现 futures::Stream<Item = Result<TicTacToeEvent, fidl::Error>>

为了在同步客户端上接收事件,FIDL 工具链会生成 wait_for_event 方法(针对 TicTacToeSynchronousProxy,它会返回一个 TicTacToeEvent

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 工具链会为TicTacToeMakeMoveResult std::result::Result<GameState, MoveError>。下面介绍 此方法的生成方式与它具有单个响应参数 result(即 类型 TicTacToeMakeMoveResult。用于成功结果的类型如下: 参数类型转换规则

未知互动处理

服务器端

当协议声明为 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 双向方法(如果方法未知) 发送至服务器,客户端将收到 Err 结果,值为 fidl::Error::UnsupportedMethodUnsupportedMethod错误只可能出现 灵活的双向方法。

除了可能会发生 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 trait for 异步客户端提供 调用 unimplemented!() 的默认实现。如前所述, 对 Proxy 类型没有影响,该类型始终会实现所有 trait 的方法。 不过,当 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);

一般界面

对于不使用 Persistent API 的高级应用场景 您也可以使用独立的编码和解码 API。这适用于 所有结构体、表和联合。有两组函数:一组用于 value 类型,另外一个用于 resource 类型。

例如,由于 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) {