Nullableなセッター/ゲッターの非サポート

ProtobufがNullableなセッターとゲッターをサポートしない理由について説明します

一部の方々から、Protocol Buffersが、それぞれのnull対応言語(特にKotlin、C#、Rust)でNullableなゲッター/セッターをサポートしてほしいというフィードバックを受けています。これはこれらの言語を使用する方々にとって役立つ機能であると思われますが、設計上の選択にはトレードオフがあり、Protobufチームはそれらを実装しないという決定を下しました。

Nullableなフィールドを持たない最大の理由は、.protoファイルで指定されたデフォルト値の意図された動作にあります。設計上、設定されていないフィールドに対してゲッターを呼び出すと、そのフィールドのデフォルト値が返されます。

注意: C#はメッセージフィールドをNullableとして扱います。この他の言語との不整合は、不変メッセージの欠如に起因しており、共有される不変のデフォルトインスタンスを作成することが不可能だからです。メッセージフィールドにはデフォルト値を持たせることができないため、これによる機能的な問題はありません。

例として、この.protoファイルを考えてみましょう。

message Msg { optional Child child = 1; }
message Child { optional Grandchild grandchild = 1; }
message Grandchild { optional int32 foo = 1 [default = 72]; }

および対応するKotlinのゲッター

// 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のゲッター

// 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)

Nullableなゲッターが存在した場合、それはユーザー指定のデフォルト値を(代わりにnullを返すために)無視せざるを得ず、驚くべき、一貫性のない動作につながるでしょう。Nullableなゲッターのユーザーがフィールドのデフォルト値にアクセスしたい場合、nullが返された場合にデフォルト値を使用するための独自のカスタム処理を記述する必要があり、これによりnullゲッターによるコードのクリーンさ/簡潔さという本来の利点が失われます。

同様に、動作が直感的でないため、Nullableなセッターは提供していません。設定を行ってから取得すると、常に同じ値が返されるとは限らず、設定を呼び出しても、そのフィールドのhas-bitに影響を与えるのは時々になるでしょう。

メッセージ型フィールドは常に明示的な存在を示すフィールド(haszer付き)であることに注意してください。Proto3では、スカラーフィールドは明示的にoptionalとマークされていない限り、暗黙的な存在(haszerなし)がデフォルトですが、Proto2は暗黙的な存在をサポートしていません。Editionsでは、暗黙的な存在機能が使用されない限り、明示的な存在がデフォルトの動作です。ほぼすべてのフィールドが明示的な存在を持つようになるという将来的な期待を考えると、Nullableなゲッターに伴うエルゴノミクス上の懸念は、Proto3ユーザーにとってよりも大きな懸念になると予想されます。

これらの問題により、Nullableなセッター/ゲッターはデフォルト値の利用方法を根本的に変更してしまいます。その潜在的な有用性は理解していますが、導入される不整合と困難さを考えると、それに見合うものではないと判断しました。