RFC-0024:强制源代码兼容性

RFC-0024:强制性来源兼容性
状态已接受
区域
  • FIDL
说明

为 FIDL 语言绑定制定源代码兼容性标准,以及制定该标准的演变流程。

作者
提交日期(年-月-日)2019-04-02
审核日期(年-月-日)2019-04-11

摘要

为 FIDL 语言绑定制定源代码兼容性标准,以及制定该标准的演变流程。

设计初衷

目前,针对语言绑定生成的代码,很少有书面规则。它们应符合特定的线程 ABI,但除此之外,绑定作者在 API 的设计方式上有很大的自由度。对 FIDL 定义所做的任何更改都可能会导致生成的绑定发生任意更改。

在实践中,用户希望看到应与源代码兼容的“常识”列表,例如定义新的顶级类型。不过,没有明确的规则表明情况就是如此。虽然这个示例看起来有点荒谬,但它说明了缺少规范会如何破坏用户的预期。实际发生的此类真实示例包括向表添加字段、添加新的 xunion 变体或向结构体添加新的默认字段。用户可以合理地期望这些更改不会破坏源代码,但没有任何标准来指定这一点,而且目前所有这些更改都会导致一个或多个语言绑定出现源代码级破坏(例如,由于 C++ 或 Go 中的有序初始化程序或 Rust 中的结构体模式)。

此外,有一些非常实用的 FIDL 语言绑定扩展在过去因与源代码兼容性相关的问题而被拒绝。例如,向不包含句柄的类型添加 copyclone 函数。包含任意句柄的类型无法克隆,因此向类型添加句柄会导致其无法提供 clone 函数(或者至少会导致其无法提供有效的克隆函数)。由于会影响源代码兼容性,一项旨在根据缺少句柄而对生成的 Rust 绑定条件性地包含 clone 函数的更改已被多次拒绝。因此,Fuchsia 开发者必须手动构建自己的 clone 函数,并为通过这些手动构建的方法调用的 FIDL 生成的类型添加封装容器类型。clone本文档提出了一种一致的标准,以便我们评估此类功能,希望能为开发者提供更符合人体工学、更易于使用且无需使用样板代码的体验。

设计

流程

此 FTP 会建立一组初始的源代码兼容性约束条件。此列表将在 Fuchsia 源代码树中的文档中跟踪。必须使用 FTP 流程添加其他源代码兼容性约束条件。为便于轻松添加与新功能相关的源代码兼容性规则,我们将修改 FTP 模板的“向后兼容性”部分,在其中添加引入新的源代码兼容性约束条件的建议(如适用)。

定义:源代码兼容性和可转换性

以下更改必须与源代码兼容(即不会破坏源代码)或可过渡

源代码兼容更改不得导致生成的 FIDL 绑定的公共 API 的任何有效(编译)用法出现源代码破坏。关于限制哪些功能属于“公共 API”以及哪些功能不属于“公共 API”的定义和可行性,存在一些合理的争论;因此,在本文档中,我们将“公共 API”定义为对生成的绑定的任何使用,而这些使用不需要使用特殊的语言技巧(例如反射)或开发者明确意图违反隐私权(例如调用 __private_dont_use_me_function_2())。必须限制公开的所有其他 API(例如位置初始化、模式匹配等),以免用户代码因对 FIDL 库进行源代码兼容的更改而遭到破坏。

可转换更改是指可以编写在更改前后都能编译的代码的更改。每个可转换的源代码兼容性规则都必须明确指定在过渡期间必须能够对 API 执行哪些“使用”。

初始源代码兼容性约束条件

下面列出了必须与源代码兼容的更改:

  • 添加新的顶级项(协议、类型或常量)。
    • 动机:用户希望能够在不破坏 FIDL 库的现有用户的情况下声明新的协议、类型和常量。
    • 例外情况:使用“*”或从命名空间中全局导入时,可能会因不同库中具有相同名称的多个项之间存在歧义而导致中断。
  • 向非严格表添加字段。
    • 动机:表旨在实现轻松扩展,并且应支持额外的字段而不会破坏。如需选择启用破坏性更改,可以使用 strict 修饰符。
  • 向非严格可扩展的联合体添加变体。
    • 动机:可扩展的联合体旨在实现轻松扩展,并且应支持额外的变体,而不会破坏。如需选择启用破坏性更改,可以使用 strict 修饰符。
  • 向非严格枚举添加成员
    • 动机:非严格枚举会隐式选择启用可扩展性,并且应在不破坏源代码的情况下可扩展。
  • 向非严格的“bits”添加成员
    • 动机:非严格位会隐式选择启用可扩展性,并且应在不破坏源代码的情况下可扩展
  • 向现有协议添加 [Layout = "Simple"]
    • 动机:[Layout = "Simple"] 的存在是为了在简单的 C 绑定中实现使用。符合规范的现有协议不应需要进行破坏性源代码更改,以指定它们可以在简单的 C 绑定中使用。
  • 向现有类型添加 [MaxHandles]
    • 动机:[MaxHandles] 旨在提供有关类型的额外信息,以便以更宽松的方式使用该类型。不应需要进行破坏性源代码更改,即可指定某个类型已包含固定的句柄数量上限,并且可以假定其继续包含的句柄数量最多不超过该上限。

下面列出了必须可过渡的更改:

  • 向方法添加 [Transitional]
    • 用途:在向该方法添加 [Transitional] 属性之前和之后,必须能够使用相同的来源实现协议并提供方法实现。
    • 动机:必须能够逐步向协议添加或移除方法,前提是所有现有实现都可以逐步调整。
  • 添加了新的 [Transitional] 方法
    • 使用:在添加新的 [Transitional] 方法之前和之后,必须能够使用相同的来源实现协议(不过,API 在过渡期间不必允许实现该方法)。
    • 动机:必须能够逐步向协议添加或移除方法,前提是所有现有实现都可以逐步调整。
  • 移除了 [Transitional] 方法
    • 使用:在移除 [Transitional] 方法之前和之后,必须能够使用相同的来源实现协议(不过,API 在过渡期间不必允许实现该方法)。
    • 动机:必须能够逐步向协议添加或移除方法,前提是所有现有实现都可以逐步调整。
  • 移除非严格表的字段
    • 用途:在移除表字段之前和之后,必须能够使用相同的来源创建表并访问其字段(要移除的字段除外)。
    • 动机:表旨在轻松演变,并且应支持在不破坏的情况下移除。如需选择启用破坏性更改,您可以在表格上使用 strict 修饰符。
  • 移除了非严格可扩展联合体的变体
    • 用途:在移除 xunion 变体之前和之后,必须能够使用相同的来源创建 xunion 并访问其变体(要移除的变体除外)。
    • 动机:xunion 旨在轻松演变,并且应支持在不破坏的情况下进行移除。如需选择启用破坏性更改,您可以在表格上使用 strict 修饰符。
  • 将类型标记为 strict
    • 用途:在添加 strict 之前和之后,必须能够使用相同的来源访问表或“位”的所有字段以及枚举或 xunion 的所有变体。
    • 动机:strict 旨在在类型稳定后添加到类型声明中,以便进行更深入的推理和使用开发者工具。不过,这只需要作为可转换的更改,而不是不可破坏的更改,因为可扩展类型可能希望允许访问无法识别的字段或变体。这些功能对 strict 类型没有意义,因为系统会拒绝无法识别的字段或变体。
  • 向枚举或位成员、表字段或可扩展联合体的变体添加 [Transitional]
    • 使用:在引入 [Transitional] 之前和之后,必须能够使用相同的来源访问所有非过渡成员/位/字段/变体,以及构建不包含 [Transitional] 值的枚举/位/表/可扩展联合值。
    • 动机:必须能够逐步移除成员、字段或变体。
  • 添加枚举或位的新成员、表的字段或标记为 [Transitional] 的可扩展联合体的变体。
    • 使用:在引入新的 [Transitional] 字段之前和之后,必须能够使用相同的来源访问所有非过渡成员/位/字段/变体,并构建不包含 [Transitional] 值的枚举/位/表/可扩展联合值。
    • 动机:必须能够逐步添加成员、字段或变体。
  • 移除枚举或位成员、表字段或标记为 [Transitional]. 的可扩展联合变体
    • 用途:在移除 [Transitional] 字段之前和之后,必须能够使用相同的来源访问所有非过渡成员/位/字段/变体,并构建不包含 [Transitional] 值的枚举/位/表/可扩展联合值。
    • 动机:必须能够逐步移除成员、字段或变体。

以下是本列表中未提及的潜在限制条件,包括未提及这些限制条件的原因:

  • 向结构体添加或从中移除字段(无论是否设为默认值)
    • 这是一项会破坏 ABI 的更改,需要进行其他大量工作才能确保兼容的过渡。若要使此更改为非破坏性更改,则需要消除对类型进行“针对所有字段”风格推理的所有内容,包括自动方法派生(例如“此类型是否包含任何浮点数”)、位置初始化程序以及详尽的字段匹配和构建。
  • 向严格表和 xunion 添加或移除字段/变体(无论是否采用默认值)
    • strict 旨在支持依赖于“for all fields”(针对所有字段)类型推理方式的其他开发者工具,包括自动方法派生(例如“此类型是否包含任何浮点值”)、位置初始化程序以及详尽的字段匹配和构建。强制将其设为非破坏性更改会妨碍这一目的。
  • 向未标记为 [MaxHandles] 的类型添加包含手柄的字段或变体
    • 由于其他原因,向严格类型或结构体添加字段已经是破坏源代码的更改,因此添加带有句柄的字段同样是破坏性更改,可能会影响生成的 API。

实施策略

此 FTP 建立了最初提议的语言兼容性标准。系统会提交 bug 并将其分配给每种语言绑定的一位作者,以确保其语言绑定符合相关要求。

工效学设计

此更改通过为源代码兼容性设置明确的标准,使 FIDL 更易于使用,支持自动检查以及更轻松地手动检查 FIDL 更改的源代码兼容性,并为绑定作者提供有关源代码兼容性的更清晰指南,让他们能够自由地创建符合语言惯用法的绑定,同时仍遵守项目的标准要求。

文档和示例

在该 FTP 获得批准后,FTP 建立的流程以及源代码兼容性规则本身将与其他 FIDL 参考文档一起发布。

向后兼容性

应用建议的准则可能需要更改绑定和对这些绑定的使用,具体取决于各个绑定作者如何应对这些更改。

本部分(“向后兼容性”部分)将修改为包含以下文字:

“如果您要引入新的数据类型或语言功能,请考虑您希望用户对 FIDL 定义进行哪些更改,以免破坏生成的代码的用户。If your feature places any new source compatibility restrictions on the generated language bindings, list those here."

请注意,您应将来源兼容性文本作为指向此 FTP 的实际链接添加,即:

[source compatibility](/docs/contribute/governance/rfcs/0024_mandatory_source_compatibility.md)

性能

此 FTP 不会限制运行时行为,但对源代码 API 的限制可能会导致语言绑定作者设计出性能较高或较低的 API。引入新的源代码兼容性限制时,应考虑以受支持的语言创建高性能绑定的可行性。

由于会推向需要更大规模内嵌和编译器优化才能实现高性能的模式(例如,将复杂的构建器 API 优化为简单的结构体初始化),因此此功能可能会影响编译时间性能。绑定作者应努力做出不会显著延长编译时间的设计选择,但特定语言 API 的编译时间后果不一定会阻止引入新的源代码兼容性限制。

安全

此功能不会影响安全性。

测试

许多源代码兼容性规则的形式为“不存在在更改前编译但在更改后无法编译的任何用户代码”。遗憾的是,这些限制很难或根本无法测试,因为它们需要枚举 API 在更改之前的每种可能用法。

不过,我们可以(并且应该)向 FIDL 更改测试套件添加项,以证明在更改之前确实存在对该 API 的某些用法,并且这些用法在更改后仍然有效。这是满足源代码兼容性要求的必要但非充分条件。

缺点、替代方案和未知情况

  • 请勿引入这样的规范。 允许绑定作者选择其更改的破坏性或非破坏性。这与当前的法律地位大致相似,但与当前系统下实际授予的灵活性相比,会为绑定作者提供更大的灵活性,因为在当前系统中,一些不利于源代码兼容性的更改会遭到反对。
  • 创建规范,说明哪些更改可以破坏源代码,而不是哪些更改不可以破坏源代码。这很难强制执行,并且需要绑定作者预测其绑定在哪些更改下必须保持源代码兼容性。
  • 稍作修改,即可同时指定没有更改,未指定的更改会默认采用某种方式。这与此 FTP 或上述替代方案本质上相同,具体取决于默认值,但它会在记录不同 FIDL 更改的影响方面建立更正式的预期。

在先技术和参考文档

我们之前曾尝试通过 [MaxHandles] 属性引入可扩展性限制。本提案的早期部分已讨论过此设计及其预期修改。