RFC-0051:更安全的 C++ 结构体 | |
---|---|
状态 | 已拒绝 |
领域 |
|
说明 | 允许 C++ 开发者编写 FIDL 代码,以便在结构体未完全初始化时在编译时中断。 |
作者 | |
提交日期(年-月-日) | 2018-07-19 |
审核日期(年-月-日) | 2019-03-14 |
遭拒原因
此 RFC 最初于 2019 年 3 月 14 日被接受。不过, implementation 从未合并,并且 C++ 绑定 自那时起发生了重大变化。认识到这一点后,我们将采取 拒绝此 RFC。
摘要
允许 C++ 开发者编写在编译时中断的 FIDL 代码 如果结构未完全初始化,则会发生此错误。
设计初衷
在 Peridot 中,我们拥有复杂的 FIDL 结构体,随着我们不断改进, 理解如何解决我们目前正面临的问题。 结构体通常深度嵌套,并在代码中远离 架构。 在对结构体进行迭代时,我们通常会对语义进行破坏性更改, 添加必填字段,或将之前的选填字段设为必填字段。 我们很难找到所有需要更新的代码。 这些错误不会显示为编译时错误,而是显示为运行时错误, 与错误初始化的代码相关联 结构体。
在更新之前,Dart 代码中 要求将所有必填字段传递到结构体中 构造函数。 此更改使 Dart 代码的开发效率和可靠性大大提高。
设计
这会修改 C++ 绑定库和代码生成器。 它不会移除任何现有接口,而只是在 构造 FIDL 结构体的实例。
这会为 FIDL 结构体添加了构建器模式。 使用它大致如下所示:
FooPtr foo = Foo::Builder()->set_bar("hello")->set_baz("world");
结构体类的 Builder() 静态方法会返回模板化构建器 对象。 构建器模板参数可捕获正在构建的结构体的类型, 类。 它存储着结构体的实例。
Field 类有两种方法:set_
name(value)
方法,用于设置
字段值,并返回从中移除该字段的构建器
构建器的模板参数,以及对Check()
可选字段;如果必填字段为 static_assert
,则会失败。
Builder 类扩展了其模板中的所有字段类型 参数,以便开发者能够访问 setter 方法。 当开发者调用 setter 并接收新的构建器类型时, 构建器模板参数 shrink 中的 field 类。 例如,省略一些模板操作:
Foo::Builder()
的会员级别为Builder<Foo, Foo::Field_bar, Foo::Field_baz>
使用 set_bar()
和 set_baz()
方法。
Foo::Builder()->set_bar(...)
的会员级别为Builder<Foo, Foo::Field_baz>
使用 set_baz()
方法。
Foo::Builder()->set_bar(...)->set_baz(...)
的会员级别为Builder<Foo>
没有任何 setter 方法。
构建器具有结构体类型的隐式转换运算符
struct 指针类型。
它们会对其余字段类型调用 Check()
方法,并返回
struct 实例。
Check()
方法要么为空操作(针对可选字段),要么是空操作
static_assert
次失败,指明未设置哪些必填字段。
文档和示例
FIDL 教程和示例将进行更新,以演示 创建结构体实例的传统方法和新方法。
向后兼容性
此方案纯粹是附加的。 它不会引入向后不兼容性。
性能
此更改不会产生运行时性能成本。 它是在 Compiler Explorer 中进行原型设计的 专门用于确保不会生成额外的代码 。
它会将一个新的头文件添加到绑定库 生成的 C++ 代码中每个结构体字段的行数。 C++ 编译器需要做一些额外的工作才能解析模板 但它没有向编译添加任何可能 产生重大影响。
安全
这一变更使我们能够将程序员错误消除运行时错误 构建时错误。 这样可以减少程序的状态空间并减少错误数量 必须正确处理和测试的用例。 减少意外行为有助于保证安全性。
测试
C++ 绑定单元测试应扩展,以测试构建器 正确设置了不同类型的字段
很难测试构建器(即 未能设置必填字段)的数据被编译器捕获。 目前尚不清楚应如何测试。
缺点、替代方案和未知问题
这会向 FIDL C++ 绑定库添加一些相当棘手的模板。 这会带来维护负担 构建时开销。
之前的模板方法使用位掩码,其模板更简单 但施加了限制,例如 64 个必填字段,增加了 FIDL 编译器。
我们还可以构建一个 linter,尝试跟踪 都已设置完毕。 这似乎是相当复杂的 Dataflow 分析。
先验技术和参考资料
Dart 绑定去年有所更改,因此结构体构造函数采用 命名参数。 必需项标记为必需项,以便 dartanalyzer 拒绝 使某些字段未初始化的更改。