FIDL 设计原则

本页总结了 FIDL 在一段时间内采用的主要设计原则。

选区的优先级

FIDL 力求遵循以下选区优先级:

  1. 用户(使用 Fuchsia 产品)
  2. 最终开发者(使用 FIDL 绑定)
  3. Fuchsia 贡献者(使用 FIDL 绑定)
  4. API 设计人员(编写 FIDL 库)
  5. Fuchsia FIDL 团队成员

此列表改编自 API 委员会章程中的名单。

ABI 优先,API 第二

RFC-0050:语法改进开始:

FIDL 主要关注定义应用二进制接口 (ABI) 问题,其次关注应用编程接口 (API) 问题。

首先采用二进制传输格式

RFC-0050:语法改进开始:

虽然许多格式可以表示 FIDL 消息,但 FIDL 线缆格式(或“FIDL 二进制线格式”)应优先处理,并且首先迎合 ...我们在选择语法时选择过度旋转到二进制 ABI 格式。

低级别优先

RFC-0131:FIDL 传输格式的设计原则开始:

如果需要牺牲高级编程(或相反)的代价来支持低级别编程,我们需要牺牲设计方面的权衡,那么我们通常会选择支持低级别编程。

功能较少

RFC-0050:语法改进开始:

我们会尽量减少功能和规则,并希望组合功能以实现各种应用场景。在实践中,在考虑新功能时,我们应该首先尝试调整或泛化其他现有特征,而不是引入新功能。

您只需要按照实际用量付费

来自 RFC-0027:您只需为实际用量付费

向 FIDL 添加功能时,我们应该评估添加该功能会对使用 FIDL 但没有使用新功能的用户带来哪些费用。然后,我们应该针对接受相应功能(这会对不使用该功能的用户带来成本)设定非常高的标准。

例如,RFC-0047: Tables 遵循了此原则,即向该语言添加表而不是替换结构体:

表肯定比结构体更复杂,因此处理表的速度较慢,且对表进行序列化会占用更多空间。因此,最好让结构体保持原样,并引入一些新内容。

相比之下,RFC-0061:可扩展联合体做出了相反的决策,即用可扩展联合替代静态联合,但这只是经过仔细权衡分析之后做出的。与表不同,可扩展联合产生的额外费用在大多数情况下是边际或不存在的。

解决实际问题

我们设计的 FIDL 是为了解决实际问题和满足实际需求,而不是想象中的需求。 我们避免设计“解决问题的解决方案”。

例如,FIDL 最初不支持空结构体,因为不清楚如何在 C/C++ 中表示空结构体。在 RFC-0056: Empty structs 中,我们发现用户采用了解决方法,并意识到需要官方解决方案。然后,我们才向该语言添加了空结构体。

根据数据进行优化

在没有数据的情况下进行优化是没有用的,至少是很危险的。在设计优化措施(例如性能、二进制文件大小)时,我们会遵循数据。

例如,RFC-0032: Efficient envelopes 最初被接受,但后来被拒绝。事后看来,当时它不应该被接受,因为没有数据可以支持它。后来,在有表明性能显著提升的数据后,该框架又被重新提出并被接受为 RFC-0113: Efficient envelopes

同样,使用稀疏表数据表示法也同样势不可挡。然而,经过调查后,设计复杂性与性能之间存在有利的权衡,导致您决定不继续发展(请参阅 RFC-0116:稀疏表)。如果没有重要的原型设计和数据收集阶段,可能会采用稀疏表,并对 Fuchsia 产生负面影响。

远处没有损坏

我们力求避免远距离损坏。在一个地方进行更改不应该在远处导致意外中断。例如,如果将名为 Foo 的结构体添加到 FIDL 文件会破坏编译,就会令人惊讶,因为代码库中完全不同的部分中的另一个 FIDL 文件已经具有名为 Foo 的类型。因此,与大多数编程语言一样,FIDL 使用命名空间来限制名称冲突的范围。

RFC-0029:增加方法序数讨论了此问题,因为它与协议组合有关。RFC-0048:显式并集序数重新访问了该主题,解释了为什么 FIDL 仅对协议使用哈希。

RFC-0057:默认无句柄时区分了值和资源类型。这样做的一个动机是,在 Rust 中为没有手柄且在一定距离处不会发生损坏的类型提供 Clone trait:

虽然 FIDL 绑定可以基于句柄的存在有条件地启用代码,但最好不要这样做,因为这会破坏可演化性保证。例如,向表添加字段通常是安全的,但添加句柄字段会破坏源代码 - 不仅对于该表而言,对于以传递方式包含该字段的所有类型而言。

RFC-0149:FIDL 编码验证不是必需的,也涉及到此主题。它会细分可能发生的破坏类别,并将是否执行编码端验证的决策移至绑定。这样一来,就可以更精细地讨论编码端验证所产生的开销的优缺点与远处发生中断的风险。

自由语法、惯用风格

我们没有严格遵循“一种方式”的理念。当我们担心用户会浪费时间在复杂的替代方案之间做出选择时,我们在 fidl-lintfidl-format(而非 fidlc)中引入了限制。

例如,FIDL 接受按任意顺序的修饰符关键字,但我们打算在 linter 中强制执行一致的排序。

再如,RFC-0040:标识符唯一性 - 通过让 fidlc 报告错误(如果任意两个标识符具有相同的规范形式),解决了标识符在大小写转换后发生冲突的问题。一种更简单的替代方案是在编译器中强制执行 FIDL 命名惯例。 不过,这样做有点过火。之所以使用不同的命名样式是有正当理由的,例如,在描述内核 API 时,强烈建议您使用 snake_case 方法。

规范表示

RFC-0131:FIDL 传输格式的设计原则开始:

FIDL 值必须有一个明确表示形式,即 FIDL 值有且仅有一个编码表示形式,以及 FIDL 值有且只能有一个解码表示形式。

FIDL 传输格式是规范的:给定消息只有一种编码。推论的结果是,每个字节都包括在内:没有字节可在不改变消息的含义的情况下更改。

例如,规范要求所有填充字节都为零。同样,RFC-0047: Tables 也不允许存储无关的空信封,以确保采用规范化表示法。

规范的表示法使 FIDL 更简单且更安全。例如,允许非零填充可能会导致 FIDL 消息泄露敏感信息,而这些信息恰好占用了内存中的这些字节。如果允许给定消息使用多种表示法,还会导致极少执行的代码路径可能会隐藏 bug,例如“超空信封”代码路径。规范表示法还可让您在不知道架构的情况下轻松比较消息是否相等:对于值类型,它是一个简单的 memcmp

无需分配

RFC-0131:FIDL 传输格式的设计原则开始:

必须能够在单次传递中编码和解码,而无需在堆栈空间之外进行分配(即没有动态堆分配)。

此要求对传输格式的设计有重大影响:您必须能够仅使用堆栈就地解码。正因如此,传输格式使用在线状态指示器和深度优先遍历顺序,而不是基于偏移的格式(例如,在解码时需要辅助数据结构跟踪信息)。

此原则与“您只需要用多少,付多少”相关,因为它满足了对 FIDL 的非常低级别的使用,而 malloc 可能尚不存在或费用非常高昂。

没有开箱即用的反射功能

RFC-0131:FIDL 传输格式的设计原则开始:

如果没有明确选择启用,则不得允许对等方对协议执行反射,无论是公开的方法还是公开类型。

传输一般性

虽然二进制传输格式排在最前面,但这并不意味着 FIDL 应该与 Zircon 通道传输紧密耦合。还有一些其他重要的用例需要考虑,例如介绍内核 API、进程内消息传递和持久性。

RFC-0050:语法改进 - 介绍了传输泛化的未来方向。

RFC-0062:方法不可能部分被拒绝,部分原因是它与 FIDL 的耦合太接近 Zircon 信道传输。