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) 都會確保結構體欄位會在初始化時使用預設值,無論是否提供預設值皆然。

因此,這項異動對安全性應該不會造成重大影響。

隱私權注意事項

不會影響隱私權。

測試

在淘汰階段期間,系統會新增測試,確保無法新增新的結構體預設值。

說明文件

結構體預設值功能的說明文件將遭到移除。

缺點、替代方案和未知事項

說明文件的預設值

部分使用者會在說明文件中使用結構體預設值,取代註解。預設結構體欄位值的好處是,它是語言中更正式的部分,且會進行型別檢查。不過,即使沒有預設值功能,您還是可以記錄使用者應將欄位初始化的值,但這必須透過註解或常數定義來完成。

與 const 的比較

在 const 會用於預設值的情況下,預設值可能會優於 const。預設值會將值附加至欄位,而非將值視為全域值,且系統會自動套用預設值,而非需要使用者手動指定。

儘管如此,預設值可以由更明確的常數取代,而使用者只需要稍微多一點工作。

更具表達力的常數

未來 const 可能會變得更具表達力,也可能會定義結構體 const。

例如:

type MyStruct = struct { x int8; };

const DEFAULT_MY_STRUCT = MyStruct{ x: 123 };

接著,在繫結中,DEFAULT_MY_STRUCT 可複製到變數 (例如 Go 中的 myStruct := DEFAULT_MY_STRUCT),並用於可設定的欄位上方做為基礎值。這與結構體中的預設值有類似的效果,但應該更容易在綁定中廣泛支援。

替代做法:為預設值提供更廣泛的支援

您可以考慮為預設值新增更廣泛的繫結支援,做為淘汰的替代方案。但這並不實際。

請考慮使用 go。產生的結構體如下所示:

type MyStruct struct {
    _ struct{}
    X int8
}

這個 API 無法自動填入預設值。此外,欄位無法「取消設定」,因此無法在編碼階段偵測未設定的欄位,並填入預設值。如要支援預設值,就必須進行重大的 API 重構。

替代方案:支援 FIDL 的零值

有些語言 (例如 Go) 和某些編碼格式 (例如通訊協定 buffers) 3 都有零值的概念。也就是說,未初始化的欄位會採用標準的「零」值,通常是 0。

您可以考慮在 FIDL 中新增對零值的支援。一方面,這會改善記憶體安全性,但另一方面,支援零值可能會與 FIDL 的「只需為所用付費」原則相衝突。雖然在某些情況下可以避免額外工作,但在其他情況下,可能需要額外步驟,確保未設定的值為零。

目前,欄位是否要初始化是繫結層級的考量,且沒有頂層規定應採取何種行為。舉例來說,Go 語言會將所有未設定的結構體欄位設為零,但 FIDL 並未強制執行這項操作。

這可能就足夠了。零值是否生效取決於使用者與繫結 API 的介面方式,因此繫結會根據情況決定在未設定欄位時會發生什麼事。部分繫結可能會要求所有欄位都必須明確設定,也就是沒有預設值。

大約一半的現有 FIDL 結構體預設值為零,另一半則不是。這表示仍需移除約一半的現有結構體預設值,或者必須將值「重新校正」,以便以零為中心 (例如使用以零為基礎的索引,而非以 1 為基礎)。

在各繫結中共用零值的概念會為 FIDL 中的零值賦予特殊意義,這在某些通訊協定中可能具有重要意義,但在其他通訊協定中則不然。在許多情況下,您可能需要明確使用名為常數,並在零值具有意義的位置使用。無論常數值是否為預設值,都可以執行此操作。

這項 RFC 的主要目標,是停止使用已損壞的功能。考量上述因素,以及零值預設值的整體約束性規定帶來的效益不明確,因此本 RFC 的設計中並未納入零值的概念。

既有技術與參考資料

自訂預設值已從 Protocol Buffers 3 版中移除 (請參閱說明文件),並改為零值預設值。這項變更可能有幾個原因。其中一個原因是,缺少預設值,因此可以使用缺少存取子的「純粹舊結構體」實作 protobuf (連結)。另一個原因是我們發現,大多數的預設值使用方式都會將預設值設為零。

在已遭拒絕的 FTP-047「必填資料表欄位」中,我們曾提議支援資料表欄位的預設值。這只是更大提案的一部分,因此拒絕這項提案並不代表結論,不一定會在資料表欄位中支援預設值。