不支持可空 Setter/Getter
我们收到了反馈,一些用户希望 protobuf 在他们选择的 null 友好语言(特别是 Kotlin、C# 和 Rust)中支持可空 Getter/Setter。虽然这对于使用这些语言的用户来说似乎是一个有用的特性,但这种设计选择存在权衡,导致 Protobuf 团队选择不实现它们。
不使用可空字段的最大原因是 .proto
文件中指定的默认值的预期行为。根据设计,对未设置的字段调用 Getter 将返回该字段的默认值。
例如,考虑以下 .proto
文件
message Msg { optional Child child = 1; }
message Child { optional Grandchild grandchild = 1; }
message Grandchild { optional int32 foo = 1 [default = 72]; }
以及相应的 Kotlin Getter
// With our API where getters are always non-nullable:
msg.child.grandchild.foo == 72
// With nullable submessages the ?. operator fails to get the default value:
msg?.child?.grandchild?.foo == null
// Or verbosely duplicating the default value at the usage site:
(msg?.child?.grandchild?.foo ?: 72)
以及相应的 Rust Getter
// With our API:
msg.child().grandchild().foo() // == 72
// Where every getter is an Option<T>, verbose and no default observed
msg.child().map(|c| c.grandchild()).map(|gc| gc.foo()) // == Option::None
// For the rare situations where code may want to observe both the presence and
// value of a field, the _opt() accessor which returns a custom Optional type
// can also be used here (the Optional type is similar to Option except can also
// be aware of the default value):
msg.child().grandchild().foo_opt() // Optional::Unset(72)
如果存在可空 Getter,它将必然忽略用户指定的默认值(以返回 null 代替),这将导致令人惊讶且不一致的行为。如果可空 Getter 的用户希望访问字段的默认值,他们将不得不编写自己的自定义处理以在返回 null 时使用默认值,这消除了使用 null Getter 带来更简洁/更容易代码的预期好处。
类似地,我们不提供可空 Setter,因为其行为将不直观。执行设置然后获取不会总是返回相同的值,并且调用设置只会有时影响字段的 has-bit。
请注意,消息类型字段始终是显式存在字段(带有 haszer)。Proto3 默认情况下,标量字段具有隐式存在(没有 haszer),除非它们被显式标记为 optional
,而 Proto2 不支持隐式存在。使用版本,显式存在是默认行为,除非使用隐式存在特性。随着几乎所有字段都将具有显式存在的预期,与可空 Getter 相关的可用性问题预计将比 Proto3 用户可能遇到的问题更令人担忧。
由于这些问题,可空 Setter/Getter 将从根本上改变默认值的使用方式。虽然我们理解其潜在的实用性,但我们认为它不值得引入不一致性和困难。