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

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

建立 FIDL 语言绑定的源代码兼容性标准,以及改进该标准的流程。

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

总结

建立 FIDL 语言绑定的源代码兼容性标准,以及改进该标准的流程。

设计初衷

目前,对于语言绑定生成的代码,很少有相关的书面规则。它们应符合特定的线路 ABI,但除此之外,绑定作者在如何构建其 API 方面拥有很大的余地。 对 FIDL 定义所做的任何更改都可能导致生成的绑定发生任意更改。

在实践中,用户希望获得一种应该与源代码兼容的“常识”列表,例如定义一个新的顶级类型。不过,没有明确的规则规定情况应如此。虽然这个例子看起来有点怪异,但它说明了,缺乏规范会如何破坏用户的期望。 在实践中发生的真实示例包括向表添加字段、添加新的 xunion 变体或向结构体添加新的默认字段。 用户可能会合理地认为,这些更改不会破坏源代码,但没有关于这一点的标准,并且所有这些更改目前都会导致一个或多个语言绑定在源代码级破坏(例如,由于 C++ 或 Go 中的位置初始化器,或 Rust 中的结构体模式)。

此外,过去还有许多对 FIDL 语言绑定非常有用的扩展,这些扩展由于与源代码兼容性之间的交互而被拒绝。例如,向不包含句柄的类型添加 copyclone 函数。包含任意句柄的类型无法克隆,因此向某个类型添加句柄会阻止该类型提供 clone 函数(或无法提供可以以任何速率运行的克隆函数)。一项变更引入了在没有句柄的情况下将 clone 函数有条件地纳入生成的 Rust 绑定,该变更已多次被拒绝,因为它对源代码兼容性产生了影响。因此,Fuchsia 开发者必须手动掷出自己的 clone 函数,并为通过这些手动方法 clone 的 FIDL 生成的类型添加封装容器类型。本文档提出了一个一致的标准,我们可以根据该标准评估此类功能,希望能够为开发者提供更符合人体工学、更加人性化且无样板代码的体验。

设计

流程

此 FTP 会建立一组初始的源代码兼容性限制。将在 Fuchsia 源代码树的一个文档中跟踪此列表。必须使用 FTP 流程添加其他源代码兼容性限制。为了方便添加与新功能相关的源代码兼容性规则,我们将修改 FTP 模板的“向后兼容性”部分,以添加一项建议来引入新的源代码兼容性限制(若有)。

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

以下更改必须是“与源代码兼容”(即不破坏源代码)或“可转换”

与源代码兼容的更改不得破坏生成的 FIDL 绑定的公共 API 的任何有效(编译)使用情况的破坏。关于限制哪些功能是“公共 API”的一部分、哪些功能并非“公共 API”的定义和可行性,存在一些合理的论点;因此,在本文档中,我们将“公共 API”视为无需进行异常语言体操(例如反射)或__private_dont_use_me_function_2()

“可转换的”更改是指可以编写在更改之前和之后都可以编译的代码的更改。每条可转换的源代码兼容性规则都必须确切指定在转换期间必须实现的 API“用途”。

初始源代码兼容性限制

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

  • 添加新的顶级项(协议、类型或常量)。
    • 动机:用户希望可以在不破坏 FIDL 库的现有用户的情况下,声明新的协议、类型和常量。
    • 豁免:如果用于从命名空间导入“*”或一揽子导入的方式,由于来自不同库、同名的多个项之间存在歧义,因此可能会出现中断。
  • 将字段添加到非严格表。
    • 动机:表专为易于扩展而设计,并且应该支持不会出现中断的其他字段。如需选择启用中断,可以使用 strict 修饰符。
  • 将变体添加到非严格可扩展联合体。
    • 动机:可扩展联合专为易于扩展而设计,并且应该支持其他变体而不出现中断。如需选择启用破坏功能,可以使用 strict 修饰符。
  • 将成员添加到非严格枚举
    • 动机:非严格枚举会隐式选择启用扩展,并且应该能够在不破坏源代码的情况下进行扩展。
  • 将成员添加到非严格“位”
    • 动机:非严格架构会隐式选择启用扩展,并且应在不破坏源代码的情况下进行扩展
  • [Layout = "Simple"] 添加到现有协议
    • 动机:[Layout = "Simple"] 的存在是为了支持在简单 C 绑定中使用。符合要求的现有协议不应要求进行破坏性源代码更改,才能指定可在简单的 C 绑定中使用这些协议。
  • [MaxHandles] 添加到现有类型
    • 动机:[MaxHandles] 旨在提供有关某个类型的额外信息,以便其使用更为宽松。它不应该要求进行重大源代码更改,即可指定某个类型包含的句柄数量已达到固定上限,并可被假定为继续包含的句柄数量不超过该上限。

下面列出了必须可转换的更改:

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

以下是此列表中已忽略的潜在限制,包括为何省略这些限制的理由:

  • 在结构体中添加或移除字段(无论是否为默认字段)
    • 这是一项破坏 ABI 的更改,需要执行其他重大工作才能确保兼容的转换。将此作为非破坏性更改需要消除任何执行类型“对所有字段”式推理的功能,包括自动方法派生(例如“此类型是否包含任何浮点数”)、位置初始化程序,以及详尽的字段匹配和构造。
  • 在严格表和 xunion 中添加或移除字段/变体(无论是否为默认项)
    • strict 旨在启用其他开发者工具,此类工具依赖于“适用于所有字段”类型的推断,包括自动方法派生(例如“此类型是否包含任何浮点数”)、位置初始化程序,以及详尽的字段匹配和构造。如果强制采用非破坏性更改,会抑制此用途。
  • 向不带 [MaxHandles] 标记的类型添加包含句柄的字段或变体
    • 由于其他原因,向严格类型或结构体添加字段已经是一项破坏源代码的更改,因此添加具有句柄的字段同样是一项破坏性更改,可能会影响因此生成的 API。

实施策略

此 FTP 用于制定最初提议的语言兼容性标准。 开发者将提交 bug,并将其分配给每种语言绑定的一位作者,以确保他们的语言绑定符合规定。

工效学设计

这项变更使 FIDL 更易于使用,因为它设置了明确的源代码兼容性标准,允许自动检查并更轻松地手动检查 FIDL 更改的源代码兼容性,并为绑定作者提供了关于源代码兼容性的更清晰指导,让他们可以自由地绑定语言惯用的绑定,同时仍然遵循项目的标准要求。

文档和示例

我们接受此 FTP 后,FTP 建立的流程以及源代码兼容性规则本身将与其他 FIDL 参考文档一起发布。

向后兼容性

应用建议的指南可能需要更改绑定和这些绑定的使用,由相应的绑定作者负责应对此类更改。

本部分(“向后兼容性”部分)将修正,以添加以下文本:

“如果您要引入新的数据类型或语言功能,请想一想您希望用户如何更改 FIDL 定义,而不破坏所生成代码的用户。 如果您的功能对生成的语言绑定施加了任何新的源代码兼容性限制,请在此处列出。"

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

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

性能

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

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

安全性

此功能不会影响安全性。

测试

许多源代码兼容性规则都采用的形式:“在此变更之前,但不存在在此变更之后编译的任何用户代码”。遗憾的是,由于这些限制要求在更改之前枚举 API 的各种可能用途,因此很难或无法测试这些限制。

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

缺点、替代方案和问题

  • 请勿引入这样的规范。 允许绑定作者选择其更改的破坏程度或非破坏性程度。这与当前的法律状态大致相似,但与当前系统下实际授予的不合规方式相比,绑定作者会获得更大的灵活性,在当前系统中,一些与源代码兼容性相关的恶意更改收到了驳回。
  • 创建一个规范,了解哪些更改可以破坏源代码,而不允许哪些更改破坏源代码。 这更难执行,并且会要求绑定作者预测到发生更改时,其绑定必须保持与源代码兼容。
  • 进行细微的修改是同时指定“是”和“非”两种更改,并且未指定的更改采用其中一种方式。这与此 FTP 或上述取决于默认设置的替代方案基本相同,尽管它在记录不同 FIDL 更改的效果方面设定了更正式的预期。

现有艺术和参考资料

之前已经尝试过通过 [MaxHandles] 属性引入演变性限制。我们已在本方案前面的部分中讨论了这种设计及其预期修改。