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

RFC-0160:移除對 FIDL 結構體預設設定的支援
狀態已接受
區域
  • FIDL
說明

此 RFC 提議移除在 FIDL 結構欄位指定預設值的功能。

問題
變更
作者
審查人員
提交日期 (年/月)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_Deprecatedd_struct_defaults」(或類似的註解),藉此在指定檔案中啟用結構預設值。

隨著時間過去,系統會移除預設值用量,並移除啟用結構預設值的指令。目前我們會完全移除支援服務。

效能

這不會影響效能。

人體工學

在支援產生預設值的繫結中,這個 RFC 可能會回到人體工學方面。但是,移除結構預設支援將可改善繫結之間的一致性,而且許多用途可以替換為 Const。

回溯相容性

這項 RFC 會中斷對現有功能的支援功能,但實作過程將會經歷一段很長的淘汰期,進而減少對現有功能的影響。

就 FIDL 來源變更而言,這項 RFC 會排除因結構預設值變更而引發複雜行為變更的危險,這點並不容易。舉例來說,明確設定初始預設值的程式碼,現在的行為與完全不設定預設值的程式碼會有所不同。此外,在不同情況下,您可能需要將所有值更改為新的預設值,或所有值都維持不變。

安全性考量

移除預設值可能會造成安全性風險,因為在某些情況下,預設值可能會初始化其他未初始化的記憶體。但實際上並非如此。HLCPP 和 Dart 這兩個繫結目前支援預設值,無論是否提供預設值,都必須確保結構化欄位已初始化。

因此,這對安全性來說應該不會有太大的影響。

隱私權注意事項

這不會影響隱私權。

測試

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

說明文件

系統將移除結構預設值功能的說明文件。

缺點、替代方案和未知

說明文件的預設值

部分使用者會使用結構體預設值來處理說明文件,而非使用註解。預設結構欄位值的優點在於更符合語言的標準部分,並且會進行類型檢查。不過,在沒有預設值功能的情況下,您仍可記錄使用者應將欄位初始化的值,但改為透過註解或常數定義完成。

與常數比較

如果使用常數做為預設值,預設值可能會優於常數。預設會將值附加至欄位,而非將其視為全域值,並自動套用預設值,無需使用者手動指定。

儘管如此,預設值還是可以替換為更明確的常數,使用者只需執行更多工作。

更生動的錐體

常數日後可能會變得更加生動,也可能定義結構常數。

例如:

type MyStruct = struct { x int8; };

const DEFAULT_MY_STRUCT = MyStruct{ x: 123 };

然後,繫結中的 DEFAULT_MY_STRUCT 可以複製到變數 (例如 Go 中的 myStruct := DEFAULT_MY_STRUCT),並在可設定欄位之上做為基礎值使用。這與結構中的預設值類似,但應該比較容易支援更廣泛的繫結。

替代做法:新增更廣泛對預設值的支援

不妨考慮為預設值新增更廣泛的繫結支援,做為淘汰的替代方案。不過,這種情況並不常見。

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

type MyStruct struct {
    _ struct{}
    X int8
}

無法透過此 API 自動填入預設值。此外,欄位無法「未設定」,因此無法在編碼階段偵測未設定欄位並填入預設值。您需要進行重大 API 重構,才能支援預設值。

替代方案:支援 FIDL 的零值支援

Go 等部分語言及部分編碼格式 (例如通訊協定緩衝區 3) 的概念是零值。也就是說,未初始化的欄位會採用標準「零」值,通常通常為 0。

您可以考慮為 FIDL 新增對零值的支援。從一方面來說,這可提升記憶體安全性,但另一方面,如果支援零值,則可能與 FIDL 原則發生衝突,也就是您「用多少付多少」的計費方式。雖然在某些情況下可能會避免執行額外操作,但在某些情況下,可能還需要採取額外步驟才能確保未設定的值為零。

目前,無論欄位是否已初始化,都是繫結層級的問題,且對於該行為沒有頂層要求。舉例來說,Go 語言零是所有未設定的結構欄位,但 FIDL 並未強制要求這類欄位。

也許已經足夠了。零值是否生效取決於使用者介面採用繫結 API 的方式,因此繫結認為在未設定欄位時會發生什麼情況,是合理的決定。有些繫結可能會要求所有欄位都必須明確設定,這代表沒有任何預設值。

將大約一半的現有 FIDL 結構體預設值設為零,一半則不是。 也就是說,您還是需要移除大約一半的現有結構體預設值,或是需要「重新校正」值,以接近零 (例如使用從零開始的索引而非以 1 為基礎的索引)。

跨繫結共用零值的概念會對 FIDL 中的值「0」產生特殊意義,在某些通訊協定中或許具有重要性,但在其他通訊協定中可能並不重要。在許多情況下,您最好採用明確表示方式,並在零值有顯著性的位置使用具名常數。無論常數值實際上是否為預設值,都可以執行上述步驟。

這項 RFC 的主要目標在於停止分散使用故障的功能。由於有別於強制要求採用零值預設值的強制性做法不夠明確,這項 RFC 的設計並未納入零值的概念。

先前的圖片和參考資料

通訊協定緩衝區第 3 版已移除自訂預設值 (請參閱說明文件),並替換為零值預設值。造成這項變動的原因有很多。其中一個原因是,沒有預設值,因而可以使用缺少存取子 (連結) 的「純舊結構」實作 protobuf。另一個原因是,它發現大部分預設值都會指派預設值為零。

在 FTP-047 遭拒的「必填資料表欄位」中,我們已提議支援資料表欄位預設值。這只是大型提案的其中一環,因此拒絕不一定代表資料表欄位是否應支援預設值。