RFC-0160:移除了对 FIDL 结构体默认值的支持

RFC-0160:取消对 FIDL 结构体默认值的支持
状态已接受
领域
  • FIDL
说明

此 RFC 提议移除在 FIDL 结构体字段上指定默认值的功能。

问题
Gerrit 更改
  • 623161
作者
审核人
提交日期(年-月-日)2021-12-29
审核日期(年-月-日)2022-05-17

总结

此 RFC 提议移除在 FIDL 结构体字段上指定默认值的功能。

设计初衷

目前可以在结构体字段上指定默认值,如以下示例所示:

type MyStruct = struct{
  x int8 = 123; // 123 is the default value
};

其目的是让结构体默认值成为所有绑定将相同值应用于未分配字段的机制,从而在绑定之间实现行为一致。但实际上并非如此。目前,只有 HLCPP 和 Dart 绑定实际上支持结构体默认值。事实上,在某些绑定(例如 Go)中,需要对网域对象进行重大重构,才能支持默认值,因为字段上的默认值没有语言级别的支持。

此外,虽然可以使用 FIDL 语言指定请求和响应结构体上的默认字段值,但这些值会被忽略,并且生成的请求和响应代码也缺少默认值。这不仅仅是因为缺乏实现,而且在我们当前的 API 中很难公开默认值。例如,在 C++ 绑定中,回复是通过函数调用进行的,但 C++ 仅支持函数调用参数的尾随默认参数。

此外,由于添加支持的复杂性和细微差别,表和联合不支持字段默认值。与结构体相比,这些可演进类型在绑定和实现方面的差异更大。

因此,对默认值的支持不一致,并且没有明确实现全面支持的明确路径。与其继续提供不一致的体验,不如完全取消支持。

除了实现方面的问题之外,还有一些关于取消对结构体默认值的支持在概念上的原因。默认值比常量更细微,因为默认属性没有由绑定用户明确分配,这种细微差别有可能让用户感到意外,并导致 bug。用户需要知道特定类型是否有默认值,以及给定绑定是否支持默认值,以知道如何使用给定类型(这相当复杂)。

利益相关方

教员:hjfreyer@google.com

审核者

ianloic@google.com、yifeit@google.com

咨询人员

azaslavsky@google.com、mkember@google.com

社交

关于 eng-council-think@fuchsia.dev 的电子邮件讨论

设计

您无法在 FIDL 中为结构体字段指定默认值。

实现

目前,在 13 个 FIDL 库中有 119 种结构体默认值使用,其中 68 个使用非零值。

默认值会立即弃用,并最终移除。

在废弃阶段,我们将引入“@allow_Deprecated_struct_defaults”(或类似)注释,以在给定文件中启用结构体默认值。

随着时间的推移,默认值使用情况将被移除,启用结构体默认值的指令也将随它们一起移除。届时,将完全停止支持。

性能

性能不会受到任何影响。

工效学设计

在支持生成默认值的绑定中,就工效学设计而言,此 RFC 可能会退而求其次。不过,移除结构体默认支持将提高绑定之间的一致性,并且许多用例可以替换为常量。

向后兼容性

此 RFC 会破坏对现有功能的支持,但实现过程将需要较长的弃用期,从而减少对功能的影响。

关于 FIDL 源代码变更之间的兼容性,此 RFC 消除了以下危险因素:结构体默认值的更改会导致难以推断的复杂行为变更。例如,显式设置初始默认值的代码现在的行为方式将与根本未设置默认值的代码不同。此外,在不同情况下,可能需要将所有值更改为新的默认值,或将所有值保持不变。

安全注意事项

移除默认值可能会带来安全风险,因为在某些情况下,默认值可能是对原本未初始化的内存进行初始化。但实际上并非如此。无论是否提供默认值,HLCPP 和 Dart(目前支持默认值的两个绑定)都会确保初始化结构体字段。

因此,应该不会对安全性产生有意义的影响。

隐私注意事项

这不会对隐私权造成任何影响。

测试

在废弃阶段,将添加测试,以确保无法添加新的结构体默认值。

文档

有关结构体默认值功能的文档将被移除。

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

文档的默认值

某些用户在文档中使用结构体默认值代替注释。默认结构体字段值的优势在于是语言更规范的部分,并且要进行类型检查。但是,如果没有默认值功能,仍然可以记录用户应将字段初始化为的值,但这将通过注释或常量定义来完成。

与常量比较

在将常量用作默认值的情况下,默认值可能优于常量。默认值会将值附加到字段,而不是被视为全局值,并且默认值会自动应用,而无需用户手动指定。

尽管如此,默认值仍可替换为更明确的常量,只需用户执行更多操作即可。

更具表现力的常量

构造函数未来可能会更具表现力,并且也有可能定义结构体。

例如:

type MyStruct = struct { x int8; };

const DEFAULT_MY_STRUCT = MyStruct{ x: 123 };

然后,在绑定中,可以将 DEFAULT_MY_STRUCT 复制到变量中(例如 Go 中的 myStruct := DEFAULT_MY_STRUCT),并将其用作可设置字段的基值。这与结构体中的默认值类似,但应该更易于在绑定中获得更广泛的支持。

替代方案:为默认值添加更广泛的支持

您可以考虑为默认值添加更广泛的绑定支持,作为弃用的替代方案。不过,这是不切实际的。

考虑使用 go。生成的结构体如下所示:

type MyStruct struct {
    _ struct{}
    X int8
}

没有位置通过此 API 自动填充默认值。此外,由于这些字段不能“未设置”,因此在编码阶段无法检测未设置的字段并填充默认值。若要支持默认值,需要进行重大的 API 重组。

替代方案:对 FIDL 使用零值支持

某些语言(例如 Go)和某些编码格式(例如协议缓冲区 3)具有零值的概念。也就是说,未初始化的字段将采用规范的“零”值,字面上通常为 0。

您可以考虑向 FIDL 添加对零值的支持。一方面,这有助于提高内存安全性,但另一方面,支持零值可能会与 FIDL 的“只用多少付多少”原则相冲突。虽然在某些情况下可以避免额外的工作,但在某些情况下,可能需要执行额外的步骤来确保未设置的值为零。

目前,是否初始化字段是一个绑定级别的问题,没有关于行为应该是什么的顶级强制性要求。例如,Go 语言会将所有未设置的结构体字段归零,但 FIDL 并不强制要求这样做。

这可能就足够了。零值是否生效取决于用户如何与绑定 API 交互,因此,对于未设置字段时会发生什么情况,绑定似乎是合理的。某些绑定可能要求必须明确设置所有字段,这意味着没有默认值。

现有 FIDL 结构体默认值中大约一半是 0,一半不是。 这意味着,仍需要移除大约一半的现有结构体默认值,或者需要“重新校准”值以以零为中心(例如,使用从 0 开始的索引,而不是从 1 开始的索引)。

跨绑定共用零值的概念会对 FIDL 中的 0 值赋予特殊含义,这在某些协议中可能具有重要意义,但在其他协议中则没有意义。在许多情况下,可能希望使用显式常量,并在零值具有显著意义的位置使用命名常量。无论常量值实际上是否为默认值,都可以执行此操作。

此 RFC 的主要目标是阻止使用已损坏的功能。鉴于这一原因,以及零值默认值的绑定级授权的不明确好处,此 RFC 的设计不包含零值概念的引入。

早期技术和参考资料

Protocol Buffer 版本 3 中移除了自定义默认值(查看文档),并替换为零值的默认值。导致这种变化的原因可能有以下几种。原因之一是,缺少默认值就能使用缺少存取器的“普通的旧结构体”来实现 protobuf(链接)。 另一个原因是,我们发现大多数使用默认值将默认值分配给零值。

在被拒的 FTP-047“必填表格字段”中,提议支持在表格字段上使用默认值。这只是一个较大的提案的一部分,因此拒绝并不一定意味着表字段应支持默认值。