不支援可為 null 的 Setter/Getter
我們收到了一些反饋,一些人希望 Protobuf 能在他們選擇的支援空值的語言中(特別是 Kotlin、C# 和 Rust)支援可空 getter/setter。雖然這對於使用這些語言的人來說似乎是一個有用的功能,但這一設計選擇存在權衡,導致 Protobuf 團隊決定不實現它們。
顯式存在並不是一個直接對映到傳統空值概念的概念。它很微妙,但顯式存在哲學更接近於“欄位不可空,但你可以檢測欄位是否被顯式賦值。如果未賦值,則正常訪問會看到一些預設值,但你可以在需要時檢查欄位是否被主動寫入。”
不支援可空欄位的最大原因是 .proto 檔案中指定預設值的預期行為。按照設計,對未設定欄位呼叫 getter 將返回該欄位的預設值。
注意: C# 確實將訊息欄位視為可空的。這種與其他語言不一致之處源於缺少不可變訊息,這使得無法建立共享的不可變預設例項。由於訊息欄位不能有預設值,因此這樣做沒有功能上的問題。
例如,考慮這個 .proto 檔案
message Msg { Child child = 1; }
message Child { Grandchild grandchild = 1; }
message Grandchild { 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 時使用預設值,這消除了可空 getter 帶來的更簡潔/更容易程式碼的所謂好處。
同樣,我們不提供可空 setter,因為其行為會不直觀。執行一次 set 後再 get 不會總是返回相同的值,並且呼叫 set 只會在某些時候影響欄位的 has-bit。
請注意,訊息型別欄位始終是顯式存在欄位(帶有 hazzer)。Proto3 預設情況下,標量欄位具有隱式存在(不帶 hazzer),除非它們被顯式標記為 optional,而 Proto2 不支援隱式存在。透過 Editions,顯式存在是預設行為,除非使用了隱式存在功能。隨著幾乎所有欄位都將具有顯式存在的預期,可空 getter 帶來的人體工程學問題預計將比 Proto3 使用者更受關注。
由於這些問題,可空 setter/getter 將徹底改變預設值的使用方式。雖然我們理解其可能的實用性,但我們已決定其引入的不一致性和困難不值得。