| RFC-0160:移除對 FIDL 結構體預設值的支援 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 這項 RFC 建議移除在 FIDL 結構體欄位中指定預設值的功能。 |
| 問題 | |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2021-12-29 |
| 審查日期 (年-月-日) | 2022-05-17 |
摘要
這項 RFC 提案是移除在 FIDL 結構體欄位中指定預設值的功能。
提振精神
目前可以在結構欄位中指定預設值,如下列範例所示:
type MyStruct = struct{
x int8 = 123; // 123 is the default value
};
目的是讓所有繫結都能透過結構體預設值機制,將相同的值套用至未指派的欄位,確保繫結之間的行為一致。不過,實際情況並非如此。 目前只有 HLCPP 和 Dart 繫結實際支援結構體預設值。事實上,在某些繫結 (例如 Go) 中,需要大幅重組網域物件,才能支援預設值,因為欄位沒有語言層級的預設值支援。
此外,雖然可以在 FIDL 語言中指定要求和回應結構體的預設欄位值,但系統會忽略這些值,且產生的要求和回應程式碼會缺少預設值。這不僅是因為缺少實作項目,在目前的 API 中,也很難公開預設值。舉例來說,在 C++ 繫結中,回覆是透過函式呼叫進行,但 C++ 只支援函式呼叫參數的尾端預設引數。
此外,由於新增支援功能時會遇到複雜和細微的問題,因此表格和聯集不支援欄位預設值,這也造成不一致的情況。相較於結構體,這些可演變的型別在繫結和實作複雜度方面有更多差異。
因此,系統對預設值的支援不一致,且沒有明確的路徑可實現完整支援。與其繼續提供不一致的體驗,不如完全移除支援。
除了實作方面的考量外,移除結構體預設值支援功能也有概念上的動機。預設值比常數更細微,因為預設值並非由繫結使用者明確指派,而這種細微差異可能會讓使用者感到意外,並導致錯誤。使用者必須知道特定型別是否有預設值,以及特定繫結是否支援預設值,才能瞭解如何使用特定型別,這相當複雜。
利害關係人
導師:hjfreyer@google.com
審查者:
ianloic@google.com、yifeit@google.com
已諮詢:
azaslavsky@google.com、mkember@google.com
社交:
在 eng-council-discuss@fuchsia.dev 進行電子郵件討論
設計
您無法在 FIDL 中指定結構欄位的預設值。
實作
目前有 13 個 FIDL 程式庫使用結構體預設值,共 119 次,其中 68 次使用非零值。
預設值會在近期內遭到淘汰,最終會移除。
在淘汰階段,系統會導入「@allow_deprecated_struct_defaults」(或類似) 註解,以便在指定檔案中啟用結構體預設值。
隨著時間推移,系統會移除預設值用法,並一併移除啟用結構體預設值的指令。屆時將完全移除支援。
效能
效能不受影響。
人體工學
在支援產生預設值的繫結中,這項 RFC 在人體工學方面似乎是退步。不過,移除結構體預設支援功能後,繫結之間的連貫性會有所提升,許多用途也能以常數取代。
回溯相容性
這項 RFC 會中斷對現有功能的支持,但實作過程會經過一段長時間的淘汰期,因此影響不大。
就 FIDL 來源變更的相容性而言,這項 RFC 移除了結構體預設值變更導致複雜行為變更的風險,這類變更難以推斷。舉例來說,明確設定初始預設值的程式碼,現在的行為會與完全未設定預設值的程式碼不同。此外,在不同情境中,您可能希望所有值都變更為新的預設值,或所有值都維持不變。
安全性考量
移除預設值可能會造成安全風險,因為在某些情況下,預設值可能會初始化未初始化的記憶體。不過,實務上並非如此。目前支援預設值的 HLCPP 和 Dart 這兩個繫結,都會確保結構體欄位已初始化,無論是否提供預設值都一樣。
因此,安全性應該不會受到顯著影響。
隱私權注意事項
不會影響隱私權。
測試
在淘汰階段,我們會新增測試,確保無法新增任何新的結構體預設值。
說明文件
系統會移除結構體預設值功能的說明文件。
缺點、替代方案和未知事項
文件預設值
部分使用者會使用結構體預設值做為說明文件,而非註解。預設結構體欄位值是語言中較正式的部分,且會經過型別檢查,因此具有優勢。不過,如果沒有預設值功能,您還是可以記錄使用者應將欄位初始化的值,但必須透過註解或常數定義來完成。
與常數的比較
在常數會做為預設值的情況下,預設值可能優於常數。預設值會將值附加至欄位,而不是將值視為全域值,且系統會自動套用預設值,使用者不必手動指定。
儘管如此,預設值仍可由更明確的常數取代,使用者只需多做一點工作。
更生動的常數
未來常數可能會變得更具表現力,並可定義結構體常數。
例如:
type MyStruct = struct { x int8; };
const DEFAULT_MY_STRUCT = MyStruct{ x: 123 };
然後,繫結可以複製到變數中 (例如 Go 中的 myStruct := DEFAULT_MY_STRUCT),並做為可設定欄位的基本值。DEFAULT_MY_STRUCT這與結構體中的預設值有類似效果,但應更容易在繫結中廣泛支援。
替代方案:新增對預設值的廣泛支援
除了淘汰功能,您也可以考慮為預設值新增更廣泛的繫結支援。不過,這並不切實際。
請考慮使用 Go。產生的結構體如下所示:
type MyStruct struct {
_ struct{}
X int8
}
這個 API 無法自動填入預設值。此外,這些欄位無法「取消設定」,因此在編碼階段無法偵測到未設定的欄位,並填入預設值。如要支援預設值,必須進行重大 API 重組。
替代方案:支援 FIDL 的零值
部分語言 (例如 Go) 和部分編碼格式 (例如通訊協定 Buffers 3) 具有零值的概念。也就是說,未初始化的欄位會採用標準的「零」值,通常是字面上的 0。
可以考慮在 FIDL 中新增對零值的支援。一方面,這會提升記憶體安全性,但另一方面,支援零值可能會與 FIDL「用多少付多少」的原則相衝突。在某些情況下,或許可以避免額外工作,但在其他情況下,可能需要額外步驟,確保未設定的值為零。
目前,欄位是否初始化是繫結層級的問題,且沒有頂層授權規定行為。舉例來說,Go 語言會將所有未設定的結構體欄位歸零,但 FIDL 並未強制執行這項操作。
這可能就足夠了。零值是否生效取決於使用者與繫結 API 的互動方式,因此繫結個別決定欄位未設定時的行為似乎合理。部分繫結可能會規定必須明確設定所有欄位,也就是沒有預設值。
現有 FIDL 結構體預設值約有一半為零,另一半則不是。這表示現有結構的預設值仍有約一半需要移除,或值需要「重新校準」為以零為中心 (例如使用從零開始的索引,而非從一開始的索引)。
如果繫結之間共用零值,FIDL 中的零值就會有特殊意義,這在某些通訊協定中可能很重要,但在其他通訊協定中則不然。在許多情況下,如果零值具有重要意義,建議您明確使用具名常數。無論常數值是否為預設值,都可以這麼做。
這項 RFC 的主要目標是停止使用有問題的功能。考量到這一點,以及強制在繫結中採用零值預設值的好處不明確,因此本 RFC 的設計未納入零值概念。
既有技術和參考資料
Protocol Buffers 第 3 版移除了自訂預設值 (請參閱文件),並以零值預設值取代。這項變更可能有多種原因。其中一個原因是,由於缺少預設值,因此可以使用「舊式結構體」實作 protobuf,但這類結構體缺少存取子 (連結)。另一個原因是,我們發現大多數使用預設值的情況,都是將預設值指派為零值。
在遭拒的 FTP-047「必要資料表欄位」中,我們提議支援資料表欄位的預設值。這只是提案的一小部分,因此遭拒不一定代表系統已決定是否應支援資料表欄位的預設值。