| RFC-0160:移除对 FIDL 结构默认值的支持 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 此 RFC 建议移除在 FIDL 结构体字段上指定默认值的功能。 |
| 问题 | |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2021-12-29 |
| 审核日期(年-月-日) | 2022-05-17 |
摘要
此 RFC 提议移除在 FIDL 结构字段上指定默认值的功能。
设计初衷
目前,可以在结构体字段上指定默认值,如以下示例所示:
type MyStruct = struct{
x int8 = 123; // 123 is the default value
};
struct 默认值的目的是为所有绑定提供一种机制,以便将相同的值应用于未分配的字段,从而实现绑定之间的一致行为。不过,实际情况并非如此。 目前,只有 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-discuss@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)和某些编码格式(例如 Protocol Buffers 3)具有零值的概念。也就是说,未初始化的字段会采用规范的“零”值,通常为字面意义上的 0。
可以考虑向 FIDL 添加对零值的支持。一方面,这会提高内存安全性;但另一方面,对零值的支持可能会与 FIDL 的“按需付费”原则相冲突。虽然在某些情况下可以避免额外的工作,但在其他情况下,可能需要执行额外的步骤来确保未设置的值为零。
目前,字段是否初始化是绑定级问题,没有关于行为应如何的顶级指令。例如,Go 语言会将所有未设置的结构体字段归零,但 FIDL 不强制要求这样做。
这可能就足够了。零值是否生效取决于用户与绑定 API 的交互方式,因此绑定单独决定在未设置字段时会发生什么情况似乎是合理的。某些绑定可能要求必须明确设置所有字段,这意味着没有默认值。
现有 FIDL 结构体默认值中,大约一半为零,一半不为零。 这意味着,大约一半的现有结构默认值仍需要移除,或者需要对这些值进行“重新校准”,使其以零为中心(例如,使用从零开始的索引而不是从一开始的索引)。
跨绑定共享零值的概念会赋予 FIDL 中的零值特殊含义,这在某些协议中可能具有重要意义,但在其他协议中则不然。在许多情况下,如果零值具有重要意义,最好明确使用命名常量。无论常量值是否为实际的默认值,都可以执行此操作。
此 RFC 的主要目标是阻止有缺陷的功能的使用范围扩大。鉴于此,以及绑定范围的零值默认设置的益处尚不明确,因此本 RFC 的设计中未纳入零值这一概念。
在先技术和参考资料
Protocol Buffers 版本 3 中移除了自定义默认值(请参阅相关文档),并替换为零值默认值。此变化可能由多种原因造成。其中一个原因是,缺少默认值使得可以使用缺少存取器的“普通旧结构体”来实现 protobuf(链接)。另一个原因是,我们发现大多数使用默认值的场景都将默认值分配为零值。
在被拒绝的 FTP-047“必需的表格字段”中,有人提议支持表格字段的默认值。这只是一个更大提案的一部分,因此拒绝并不一定意味着就表字段是否应支持默认值得出结论。