| RFC-0024:强制性源代码兼容性 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 为 FIDL 语言绑定建立源代码兼容性标准,并建立演变该标准的过程。 |
| 作者 | |
| 提交日期(年-月-日) | 2019-04-02 |
| 审核日期(年-月-日) | 2019-04-11 |
摘要
为 FIDL 语言绑定建立源代码兼容性标准,并建立演变该标准的过程。
设计初衷
如今,对于语言绑定生成的代码,书面规则很少。 预计它们将符合特定的线路 ABI,但除此之外,绑定作者在如何塑造其 API 方面拥有很大的自由度。对 FIDL 定义的任何更改都可能导致生成的绑定发生任意更改。
实际上,用户希望获得一份“常识性”列表,其中列出应与源代码兼容的事项,例如定义新的顶级类型。 但是,没有明确的规则说明情况就是如此。虽然这种情况看起来有些荒谬,但它说明了缺乏规范可能会让用户失望。实践中发生的实际示例包括向表中添加字段、添加新的 xunion 变体或向结构体添加新的默认字段。 用户可以合理地预期这些更改不会破坏源代码,但没有标准对此进行指定,并且所有这些更改如今都会导致一个或多个语言绑定中的源代码级别中断(例如,由于 C++ 或 Go 中的位置初始化程序,或 Rust 中的结构体模式)。
此外,过去有许多对 FIDL 语言绑定非常有用的扩展因与源代码兼容性交互而被拒绝。
这方面的示例包括向不包含句柄的类型添加 copy 或
clone 函数。
包含任意句柄的类型无法克隆,因此向类型添加句柄
会阻止其提供 clone 函数(或者至少阻止其
提供有效的克隆函数worked)。
一项根据句柄的缺失情况向生成的 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修饰符。
- 设计初衷:可扩展联合旨在轻松扩展,并且应支持添加变体而不中断。
如需选择中断,可以使用
- 向非严格枚举添加成员
- 设计初衷:非严格枚举隐式选择可扩展性,并且应可扩展而不中断源代码。
- 向非严格“位”添加成员
- 设计初衷:非严格位隐式选择可扩展性,并且应可扩展而不中断源代码
- 向现有协议添加
[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旨在启用其他开发者工具,这些工具依赖于对类型进行“针对所有字段”式推理,包括自动方法派生(例如“此类型是否包含任何浮点数”)、位置初始化程序以及详尽的字段匹配和构造。强制将其作为非中断更改会阻碍此目的。
- 向未标记
[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 用法。 这是满足源代码兼容性要求的必要但非充分条件。
缺点、替代方案和未知因素
- 不引入此类规范。 允许绑定作者选择他们希望更改是中断还是非中断。 这与当前的法律地位大致相似,但与当前系统下事实上的授权相比,它将为绑定作者提供更大的灵活性,在当前系统中,一些对源代码兼容性不利的更改受到了抵制。
- 为允许中断源代码的更改创建规范,而不是为不允许中断源代码的更改创建规范。这更难强制执行,并且需要绑定作者预测其绑定必须保持源代码兼容的更改。
- 稍作修改即可指定哪些更改是 are,哪些更改 不是,未指定的更改默认采用一种方式或另一种方式 - 这 本质上与此 FTP 或上述替代方案相同 ,具体取决于默认值,尽管它围绕记录不同 FIDL 更改的效果设置了更正式的预期 。
在先技术和参考文档
之前曾尝试通过 [MaxHandles] 属性引入可演变性限制。
本提案的较早部分讨论了此设计及其预期修改。