Keine unterstützten null-fähigen Setter/Getter

Erklärt, warum Protobuf keine nullable Setter und Getter unterstützt

Wir haben Feedback erhalten, dass einige Leute sich wünschen würden, dass Protobuf in ihrer null-freundlichen Sprache ihrer Wahl (insbesondere Kotlin, C# und Rust) null-fähige Getter/Setter unterstützt. Auch wenn dies für Benutzer dieser Sprachen eine hilfreiche Funktion zu sein scheint, hat die Designentscheidung mit Kompromissen zu tun, die das Protobuf-Team dazu veranlasst haben, sie nicht zu implementieren.

Explizite Präsenz ist kein Konzept, das direkt der traditionellen Vorstellung von Nullability entspricht. Es ist subtil, aber die Philosophie der expliziten Präsenz ist näher daran, "die Felder sind nicht nullfähig, aber Sie können erkennen, ob dem Feld explizit ein Wert zugewiesen wurde oder nicht. Der normale Zugriff sieht einen Standardwert, wenn er nicht zugewiesen ist, aber Sie können überprüfen, ob das Feld aktiv beschrieben wurde oder nicht, wenn nötig."

Der wichtigste Grund, keine nullfähigen Felder zu haben, ist das beabsichtigte Verhalten von Standardwerten, die in einer .proto-Datei angegeben sind. Per Design gibt das Aufrufen eines Getters für ein nicht gesetztes Feld den Standardwert dieses Feldes zurück.

Hinweis: C# behandelt Nachrichtenfelder als nullfähig. Diese Inkonsistenz mit anderen Sprachen rührt von der fehlenden Unveränderlichkeit von Nachrichten her, die die Erstellung von gemeinsam nutzbaren unveränderlichen Standardinstanzen unmöglich macht. Da Nachrichtenfelder keine Standardwerte haben können, gibt es hierbei kein funktionelles Problem.

Betrachten Sie als Beispiel diese .proto-Datei

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

und die entsprechenden 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)

und die entsprechenden 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)

Wenn ein nullfähiger Getter existieren würde, würde er notwendigerweise die vom Benutzer angegebenen Standardwerte ignorieren (um stattdessen null zurückzugeben), was zu überraschendem und inkonsistentem Verhalten führen würde. Wenn Benutzer von nullfähigen Gettern auf den Standardwert des Feldes zugreifen möchten, müssten sie ihre eigene benutzerdefinierte Logik schreiben, um den Standardwert zu verwenden, wenn null zurückgegeben wird, was den angeblichen Vorteil von saubererem/einfacherem Code mit Null-Gettern zunichtemacht.

Ähnlich bieten wir keine nullfähigen Setter an, da das Verhalten unintuitiv wäre. Das Ausführen eines Sets und dann eines Gets würde nicht immer denselben Wert zurückgeben, und das Aufrufen eines Sets würde nur manchmal den "has-Bit" für das Feld beeinflussen.

Beachten Sie, dass Felder vom Nachrichtentyp immer explizite Präsenzfelder sind (mit "hazzers"). Proto3 hat standardmäßig implizite Präsenz für Skalarfelder (ohne "hazzers"), es sei denn, sie sind explizit als optional markiert, während Proto2 keine implizite Präsenz unterstützt. Mit Editionen ist die explizite Präsenz das Standardverhalten, es sei denn, eine Funktion für implizite Präsenz wird verwendet. Mit der Erwartung, dass fast alle Felder explizite Präsenz haben werden, werden die ergonomischen Bedenken im Zusammenhang mit nullfähigen Gettern voraussichtlich mehr Bedeutung haben, als sie für Proto3-Benutzer möglicherweise hatten.

Aufgrund dieser Probleme würden nullfähige Setter/Getter die Art und Weise, wie Standardwerte verwendet werden können, radikal ändern. Obwohl wir den möglichen Nutzen verstehen, haben wir entschieden, dass er die Inkonsistenzen und Schwierigkeiten, die er mit sich bringt, nicht wert ist.