RFC-0061:可扩展联合 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 为了提供更多方式来表达形状可能需要随时间演变的载荷,我们提议用可扩展的并集取代当今存在的并集。 |
作者 | |
提交日期(年-月-日) | 2018-09-26 |
审核日期(年-月-日) | 2018-10-11 |
“夏威夷和阿拉斯加餐饮”
摘要
提供更多方式来表达形状可能需要演变的载荷 我们提议用取代目前存在的工会 可扩展联合的设计。
设计初衷
如今,工会无法随着时间的推移而发展,我们甚至警告过 “一般来说,更改并集的定义将破坏二进制 兼容性。"
目前定义的很多联合体的可扩展性 例如 fuchsia.modular/TriggerCondition, 其中字段已废弃而不被移除,或 fuchsia.modular/Interaction.
如后所述,
还有许多联合,其当前表示法是
因为它们不太可能在近期发展。不过,
同时保留 static unions
和 extensible unions
引入了
不必要的复杂度,请参阅
优缺点。
设计
如需引入可扩展联合,我们需要修改 FIDL 的多个部分:
语言和 fidlc
、JSON IR、传输格式以及所有语言
绑定。
我们还需要在不同的地方对此新功能进行记录。
我们将逐一讨论各项更改。
语言
从语法上讲,可扩展并集与静态并集完全相同:
union MyExtensibleUnion {
Type1 field1;
Type2 field2;
...
TypeN fieldN;
}
在后台,系统会为每个字段分配一个序数: 对表的 序号,以及如何 方法'序数词 会自动获得分配
具体而言:
- 序数的计算算法与方法序数相同
(详细信息),
我们将库名称串联起来,
“
.
”、可扩展的联盟名称“/
”以及成员名称 然后采用 SHA256 算法,并使用0x7fffffff
进行遮盖。 - 序数为
uint32
,没有两个字段可以声明相同的序数, 并禁止0
。 在发生顺序冲突的情况下,[Selector]
属性 用于提供备用名称(或重命名后的成员)。 - 序数可以是稀疏的,也就是说,与表的工作方式不同,表需要 密集序数。
- 在可扩展联合中,不允许使用可为 null 的字段。
- 可扩展联合必须至少有一个成员。
可扩展并集可用于当前可使用并集的任何位置 语言。 尤其是:
- 结构体、表和可扩展联合可以包含可扩展联合体;
- 可扩展联合可以包含结构体、表和可扩展联合体;
- 接口参数或返回值可以是可扩展的并集;
- 可扩展联合可以为 null。
JSON IR
在下表中,我们将在每个联合字段声明中添加一个键 “ordinal”。
电汇格式
在电线上,可扩展并集由 区分各个选项(填充到 8 个字节),后跟 制作人熟知的各种成员的信封。 具体而言,即:
uint32
标记,其中包含要编码的成员的序号;uint32
内边距,用于对齐 8 个字节;- 一个
uint32
num_bytes,用于存储信封中的字节数; 始终是 8 的倍数,如果信封为 null,则必须为 0; uint32
num_handles,用于存储信封中的句柄数量, 如果信封为 null,则必须为 0;uint64
data 指针,用于指示是否存在 离线数据:- 当信封为 null 时,值为
0
; - FIDL_ALLOC_PRESENT(或 UINTPTR_MAX) 和下一个行外对象;
- 当信封为 null 时,值为
- 在解码以供使用时,此 data 指针要么是 nullptr 若信封为 null,则为指向信封的有效指针。
- 信封会为 内容。
可为 null 的可扩展联合的 tag 为 0,num_bytes 设置为 0, num_handles 设置为 0,并且数据指针为 FIDL_ALLOC_ABSENT, 即0。 从本质上讲,null 可扩展联合是 24 个字节,即 0。
语言绑定
可扩展并集与并集类似,不同之处在于前者需要 处理“未知”错误,读取 union 时的情况。 理想情况下,大多数语言绑定会将
union Name { Type1 field1; ...; TypeN fieldN; };
就像它们是一个可扩展的并集一样,可以轻松地 对未知情况进行模数支持, 只有在可扩展并集时才有意义。
首先,我们建议不要通过任何语言绑定公开预留成员: 它们存在于 JSON IR 中以保持完整性,我们不希望 在语言绑定中公开这些标识符很有用。
实施策略
植入过程将分两步完成。
首先,我们将构建对可扩展联合体的支持:
- 通过使用不同语言(
fidlc
)来介绍该功能, 关键字 (xunion
),用于区分静态联合体和可扩展式 并集。 - 实现各种核心语言绑定(C、C++、Rust、Go、Dart)。 相应地扩展兼容性测试和其他测试。
其次,我们将所有静态联合体迁移到可扩展联合体:
为静态联合生成序数,并将其放在 JSON IR 中。 后端最初应忽略这些内容。
在读取路径上,同时具有两种读取联合模式,就像它们是 静态并集,就好像它们是可扩展的并集(需要序数 )。 根据事务中的标志,在一种与另一种方法之间进行选择 消息标头。
更新写入路径以将联合编码为可扩展并集,并指明 通过在交易消息标头中设置该标志来尽可能多地处理。
当所有写入者都完成更新、部署和传播后, 静态联合处理和软转换的基架代码。
文档和示例
这至少需要在以下位置提供文件:
向后兼容性
可扩展联合与“静态”明确不兼容 并集。
性能
不使用时对性能没有影响。 在构建期间对性能的影响微乎其微。
安全
不影响安全性。
测试
编译器中的单元测试、在各种环境中编码/解码的单元测试 语言绑定和兼容性测试,用于检查各种语言 绑定在一起。
缺点、替代方案和未知问题
可扩展联合的效率低于不可扩展联合。 此外,不可扩展的并集无法通过其他方式表达 。 因此,我们提议同时使用这两种功能。
不过,我们可以决定只存在可扩展的并集,
而不再是当前定义的联合体。
这会违反紫红色各地区的规定,这些地方代表工会
对性能至关重要的消息,
期望值,例如fuchsia.io/NodeInfo
、fuchsia.net/IpAddress
。
保持静态联合的优缺点
优点
- 与并集相比,可扩展联合会产生 8 个字节的费用(对于 信封大小和手柄数量)。 此外,可扩展联合的数据始终离线存储 (即,数据指针需要额外的 8 个字节),而只有 nullable unions'数据离线存储
- 由于联合的编码,无法使用 FIDL 中的其他基元。 因此,如果从语言、某些类别的邮件中 我们无法再以紧凑而高效的方式表示。
- 在某些情况下,联合可表示为
但方式不同;但这属于例外,并非常态。
一个可以在不使用并集的情况下进行重写的例子是
fuchsia.net.stack/InterfaceAddressChangeEvent
仅用于
fuchsia.net.stack/InterfaceAddressChange
其中,InterfaceAddress 可以直接写入,并且具有
enum
以指明添加还是移除。
缺点
- 同时保留静态并集和可扩展联合体会增加 编译器、JSON IR、所有后端以及编码/解码。 收益微乎其微:在 FIDL 的世界,应用大小差异是微乎其微的 编码最初并不是特别适合大小。 此外,可根据需要对可扩展联合进行解码。
- 下面以收益微乎其微的方式为例,
fuchsia.io/NodeInfo:
<ph type="x-smartling-placeholder">
- </ph>
- 目前,NodeInfo 有 6 个选项:服务(大小 1)、文件(大小 4)、 目录(大小 1)、竖线(大小 4)、vmofile(大小 24)、设备(大小 4)。
- 因此,NodeInfo 的总大小始终为 32 个字节,即 代码 + max(选项大小)= 8 + 24 = 32。
- 使用可扩展联合时,NodeInfo 的大小取决于选项 正在编码。 始终有 16 个字节的“tax”(对比 8),因此相应的尺寸 be:服务 = 24,文件 = 24,目录 = 24,竖线 = 24,vmofile = 40, 设备 = 24。
- 因此,在任何情况下,我们都削减了 8 个字节, vmofile,其中要再添加一个 8 个字节。
- 同时具有静态并集和可扩展的语言的复杂性 也是一个担忧 我们希望库作者在使用两种方法之间左右摇摆, 选择可扩展联合是一种更安全的长期选择,而且成本很低。
总而言之,我们决定用可扩展的联合体取代静态联合体。
标记与序数
我们使用 ordinal 表示分配给字段的内部数值,
即,通过哈希处理计算得出的值。
我们使用标记来表示绑定中变体的表示形式:
在 Go 中,可能是类型为 alias
的常量;在 Dart 中,这可能是 enum
。
fidlc
编译器只处理序数。
开发者很可能只处理代码。
绑定可提供从高级标记到低级标记的转换
内部序数。
无空的可扩展联合体
在设计阶段,我们考虑过让可扩展联合体为空。 不过,我们最终选择禁止这种行为:选择一个可为 null 的 明显具有单个变体(例如空结构体)的可扩展并集 对意图进行建模。 这样也避免了两个“单位”例如, null 值和一个空值。
先验技术和参考资料
- 协议缓冲区具有 oneof。
- 除非在特殊情况下,否则 FlatBuffer 的联合不可扩展。