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:表格遵循了这一原则,通过向语言添加表格而不是替换结构体来实现:

表必然比结构体更复杂,因此处理表的速度会更慢,序列化表会占用更多空间。因此,最好保持结构不变,并引入新内容。

相比之下,RFC-0061:可扩展的联合 在仔细分析权衡后,做出了用可扩展的联合替换静态联合的相反决定。与表不同的是,可扩展联合带来的额外费用在大多数情况下微乎其微,甚至没有。

解决实际问题

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

例如,FIDL 最初不支持空结构体,因为不清楚如何在 C/C++ 中表示它们。在 RFC-0056:空结构体中,我们看到用户正在使用解决方法,并认识到需要一个正式的解决方案。之后,我们才向该语言添加了空结构。

根据数据进行优化

在没有数据的情况下进行优化,轻则毫无用处,重则会带来危险。在设计优化(例如性能、二进制大小)时,我们会遵循数据。

例如,RFC-0032:高效信封 最初被接受,但后来被拒绝。事后看来,当时不应接受此项研究,因为没有数据支持。后来,在有数据表明性能显著提升后,该提案被重新提出并被接受为 RFC-0113:高效信封

同样,使用更稀疏的表格数据表示形式也获得了显著的推动力。不过,经过调查,我们发现设计复杂性和性能之间存在不利的权衡,因此决定不继续推进(请参阅 RFC-0116:稀疏表)。如果没有重要的原型设计和数据收集阶段,我们很可能会采用更稀疏的表,从而对 Fuchsia 产生负面影响。

远距离无破损

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

RFC-0029:增加方法序号讨论了与协议组成相关的此问题。RFC-0048:显式联合序号重新探讨了该主题,解释了为什么 FIDL 仅对协议使用哈希。

RFC-0057:默认无句柄引入了值类型和资源类型之间的区别。这样做的原因之一是,在 Rust 中为没有句柄的类型提供 Clone 特征,而不会造成远程中断:

尽管 FIDL 绑定可以根据是否存在句柄有条件地启用代码,但这样做是不理想的,因为它会破坏可演化性保证。例如,向表中添加字段通常是安全的,但添加句柄字段会成为源中断变更,不仅对于该表,而且对于以传递方式包含该表的所有类型都是如此。

RFC-0149:FIDL 编码验证不是强制性的 也涉及此主题。它细分了可能发生的损坏类别,并将是否执行编码端验证的决定移至绑定。这样一来,我们就可以更精细地讨论编码端验证的开销与远程损坏风险之间的优缺点。

宽松的语法,地道的风格

我们不会严格遵循“一种方法”的理念。如果我们担心用户会浪费时间在微不足道的替代方案之间做出选择,那么我们会在 fidl-lintfidl-format 中引入限制,而不是在 fidlc 中引入限制。

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

再举一个例子,RFC-0040:标识符唯一性通过让 fidlc 在任意两个标识符具有相同的规范形式时报告错误,修复了标识符在大小写转换后发生冲突的问题。一种更简单的替代方案是在编译器中强制执行 FIDL 命名惯例。不过,这有点过头了。使用不同的命名样式有其合理原因,例如在描述内核 API 时,强烈建议使用 snake_case 方法。

规范表示法

根据 RFC-0131:FIDL 线格式的设计原则

FIDL 值必须具有明确的单一表示形式,即 FIDL 值只有一种编码表示形式和一种解码表示形式。

FIDL 有线格式是规范的:对于给定的消息,只有一种编码。作为推论,每个字节都必须有意义:任何字节都不能在不改变消息含义的情况下进行更改。

例如,规范要求所有填充字节均为零。同样,RFC-0047:表格 禁止存储无关的空信封,以确保规范表示。

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

无需分配

根据 RFC-0131:FIDL 线格式的设计原则

必须能够在一次传递中进行编码和解码,而无需分配超出堆栈空间的内存(即无需动态堆分配)。

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

此原则与“您只需为实际使用的服务付费”相关,因为它适用于 malloc 可能尚不存在或成本过高的极低级 FIDL 用例。

没有开箱即用的反射功能

根据 RFC-0131:FIDL 线格式的设计原则

未经明确选择启用,不得允许对等方对协议(无论是公开方法还是公开类型)执行反射。

传输通用性

虽然二进制线格式是首要考虑因素,但这并不意味着 FIDL 应与 Zircon 渠道传输紧密耦合。还有其他重要的使用情形需要考虑,例如描述内核 API、进程内消息传递和持久性。

RFC-0050:语法改版描述了传输泛化的未来方向。

RFC-0062:不可能的方法之所以被部分拒绝,是因为它将 FIDL 与 Zircon 渠道传输紧密耦合。