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
};
其目的是让结构体默认值成为一种机制,以便所有绑定都将相同的值应用于未分配的字段,从而使绑定之间的行为保持一致。但在实践中,情况并非如此。目前,只有 HLCPP 和 Dart 绑定实际上支持结构体默认值。事实上,在 Go 等某些绑定中,由于没有语言级别对字段默认值的支持,因此需要对域对象进行重大重构才能支持默认值。
此外,虽然可以使用 FIDL 语言指定请求和响应结构体上的默认字段值,但系统会忽略这些值,并且为请求和响应生成的代码缺少默认值。这不仅仅是因为缺少实现,我们目前的 API 中很难公开默认值。例如,在 C++ 绑定中,回复是通过函数调用进行的,但 C++ 仅支持函数调用参数的尾随默认参数。
除了不一致之外,表和联合体也不支持字段默认值,因为添加支持的复杂性和细微之处较多 - 与结构体相比,这些可演化类型在绑定和实现复杂性方面存在更多差异。
因此,对默认值的支持不一致,并且没有实现全面支持的明确路径。与其继续提供不一致的体验,不如彻底移除支持。
除了实现方面的问题之外,我们移除对结构体默认值的支持还有一些概念性原因。默认值比 const 更细微,因为它们不是由绑定用户显式分配的,这种细微之处可能会让用户感到意外并导致 bug。用户需要知道特定类型是否具有默认值,以及给定绑定是否支持默认值,才能知道如何使用给定类型,这非常复杂。
利益相关方
主持人:hjfreyer@google.com
Reviewers:
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 是目前支持默认值的两个绑定,它们可确保无论是否提供了默认值,都会初始化结构体字段。
因此,对安全性的影响应该不大。
隐私注意事项
不会对隐私造成影响。
测试
在废弃阶段,我们会添加测试,以确保无法添加新的结构体默认值。
文档
结构体默认值功能的文档将被移除。
缺点、替代方案和未知情况
文档的默认值
有些用户会使用结构体默认值来代替注释。默认结构体字段值的好处在于,它们是语言中更为正式的部分,并且会进行类型检查。不过,即使没有默认值功能,您仍然可以记录用户应将字段初始化为的值,但需要通过注释或 const 定义来完成。
与常量进行比较
在 const 被用作默认值的情况下,默认值可能优于 const。默认值会将值附加到字段,而不是将值视为全局值,并且系统会自动应用默认值,而无需用户手动指定。
尽管如此,默认值可以替换为更明确的常量,而用户只需多做一些工作。
更具表现力的常量
常量未来可能会变得更具表达力,并且可以定义结构体常量。
例如:
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 结构体默认值为零,另一半则不是。这意味着,仍需要移除约一半现有的结构体默认值,或者需要对值进行“重新校准”,使其围绕零对称(例如,使用从零开始的索引,而不是从 1 开始的索引)。
在多个绑定中共享零值的概念会为 FIDL 中的零值赋予特殊含义,该值在某些协议中可能具有重要意义,但在其他协议中则不然。在许多情况下,在零值具有重要意义的地方,最好明确使用命名常量。无论常量值实际上是否为默认值,都可以执行此操作。
此 RFC 的主要目标是阻止使用已损坏的功能。鉴于这一点以及强制要求全局使用零值默认值的好处不明确,因此本 RFC 的设计中未引入零值的概念。
在先技术和参考文档
自定义默认值已从 Protocol Buffers 版本 3 中移除(请参阅文档),并替换为值为零的默认值。造成这种变化的原因可能有多种。原因之一是,由于没有默认值,因此可以使用缺少访问器的“普通旧结构体”实现 protobuf (link)。另一个原因是,我们发现大多数默认值用法都将默认值分配为零值。
在被拒的 FTP-047“必填表格字段”中,我们提出了对表格字段的默认值的支持。这只是一个更大提案的一部分,因此遭拒并不一定意味着我们就是否应支持表格字段的默认值得出结论。