RFC-0160:移除對 FIDL 結構預設值的支援

RFC-0160:移除對 FIDL 結構體預設值的支援
狀態已接受
區域
  • 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「必要資料表欄位」中,我們提議支援資料表欄位的預設值。這只是提案的一小部分,因此遭拒不一定代表系統已決定是否應支援資料表欄位的預設值。