不支持可空 Setter/Getter

解释了 Protobuf 为什么不支持可空 setter 和 getter

我们收到反馈,一些人希望 protobuf 在他们选择的空值友好的语言(特别是 Kotlin、C# 和 Rust)中支持可空 getter/setter。虽然这对于使用这些语言的人来说似乎是一个有用的功能,但设计选择是有权衡的,这导致 Protobuf 团队选择不实现它们。

不支持可空字段的最大原因是 .proto 文件中指定的默认值的预期行为。根据设计,在未设置的字段上调用 getter 将返回该字段的默认值。

注意: C# 确实将消息字段视为可空。与其他语言的这种不一致性源于缺少不可变消息,这使得创建共享的不可变默认实例成为不可能。由于消息字段不能有默认值,因此这没有功能问题。

例如,考虑这个 .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,因为该行为将是非直观的。执行 set 然后 get 并不总是返回相同的值,并且调用 set 有时只会影响字段的 has-bit。

请注意,消息类型的字段始终是显式存在字段(带有 hazzers)。Proto3 默认情况下标量字段具有隐式存在(没有 hazzers),除非它们被显式标记为 optional,而 Proto2 不支持隐式存在。使用 版本,显式存在是默认行为,除非使用隐式存在功能。由于向前预期几乎所有字段都将具有显式存在,因此与 Proto3 用户相比,可空 getter 带来的符合人体工程学的担忧预计将更加令人担忧。

由于这些问题,可空 setter/getter 将从根本上改变默认值的使用方式。虽然我们理解可能的实用性,但我们已决定它不值得引入的不一致性和困难。