RFC-0087:RFC-0050 更新:FIDL 方法參數語法

RFC-0087:RFC-0050 更新:FIDL 方法參數語法
狀態已接受
領域
  • FIDL
說明

明確定義頂層類型,藉此修改指定要求和回應參數的語法。

問題
毛皮變化
作者
審查人員
提交日期 (年-月-日)2021-03-09
審查日期 (年-月-日)2021-04-14

摘要

RFC-0050 之後,FIDL 方法要求或回應的參數將 以 (name1 Type1, name2 Type2) 格式內嵌 (以隱含形式) 使用參數做為結構的成員,定義結構體的要求/回應類型。 這個 RFC 建議變更語法,明確指定頂層類型, 例如:(struct { name1 Type1; name2 Type2; })。使用者也可以 除了結構體外,也指定聯集或資料表的要求/回應類型。這個 RFC 對線格式沒有影響。

另請參閱:

術語

在此 RFC 中,「wrapping a type in struct in struct」(在結構體中包裝類型)是指將 ID 並定義由該 Y 元件組成的新結構體 類型。舉例來說,在結構中包裝 T 類型,指的是定義新的類型 type Wrapped = struct { my_t T; }。這項技巧適用於 對於 FIDL 語言的特定限制。舉例來說,uint8 不得 可為空值,但結構體可以有效擁有可為空值的 uint8。值得注意的是,TWrapped 的線路格式完全相同。

提振精神

變更語法,明確指定頂層類型,例如:(struct { name1 Type1; name2 Type2; }) 透過以下方式提供兩個主要優點:

  • 將 ABI 的影響放在語法的最前處,遵循 FIDL 的 設計原則。例如,編寫 (struct { name1 Type1; }) 而非 (name1 Type1),會明確表示 要求或回應類型屬於結構體,因此會新增或移除 新參數與 ABI 或 API 不相容。
  • 這樣就能讓使用者指定不同的頂層類型 需要額外的間接層級包裝類型在結構體中。 除了透過內嵌定義提升可讀性, FIDL 編譯器會挑選適當的名稱,而不是減輕負擔 開發人員。其中一個可能的做法是 請求參數的擴充性是優先考量 時,使用者可以使用資料表而非結構體。

這項 RFC 導入時間可透過以下兩種方式連結至 RFC-0050

  • RFC 引入了匿名版面配置,可讓您重複使用版面配置 這個語法可以指定要求/回應類型 並另外命名
  • 這個 RFC 中提議的語法變更可分組至現有 RFC-0050 需要實作和遷移,因此不需要 獨立遷移作業

設計

語法

變更前:

protocol Oven {
  StartBake(temp Temperature);
  // message with no payload
  -> OnReady();
};

變更後:

protocol Oven {
  StartBake(struct { temp Temperature; });
  // message with no payload
  -> OnReady();
};

完整的可用方法變化版本如下:

MyMethod(struct { ... }) -> (struct { ... });   // Two-way
MyMethod(struct { ... }) -> ();                 // Two-way, but response is empty
MyMethod() -> (struct { ... });                 // Two-way, but request is empty
MyMethod() -> ();                               // Two-way, but both request and response are empty
MyMethod() -> (struct { ... }) error zx.status; // Two-way; response leverages error syntax
MyMethod() -> () error zx.status;               // Error: must specify a type for success case.
MyMethod(struct { ... });                       // One-way
MyMethod();                                     // One-way, but request is empty
-> MyMethod(struct { ... })                     // Event
-> MyMethod();                                  // Event, but response is empty

更正式的

protocol-method = ( attribute-list ) , IDENTIFIER , parameter-list,
                  ( "->" , parameter-list , ( "error" type-constructor ) ) ;
protocol-event = ( attribute-list ) , "->" , IDENTIFIER , parameter-list ;
parameter-list = "(" , ( parameter ( "," , parameter )+ ) , ")" ;
parameter = ( attribute-list ) , type-constructor , IDENTIFIER ;

會變成:

protocol-method = ( attribute-list ) , IDENTIFIER , method-params
                  ( "->" , method-params ( "error" type-constructor ) ) ;
protocol-event = ( attribute-list ) , "->" , IDENTIFIER , method-params;
method-params = "(" , type-constructor , ")"

type 如 RFC-0050 中所定義,即 參照現有類型 (例如 MyType<args>:constraints) 或匿名 版面配置 (例如struct { name Type; }:constraints

雖然文法允許任意類型做為要求和 FIDL 編譯器會驗證頂層類型 結構體、聯集或資料表

如同 RFC-0050指定的,編譯器保留了 任何內嵌的頂層要求或回應類型,因此能夠將 而不是以內嵌樣式為依據 (例如 可讀性)。舉例來說 可能變更為:

protocol MyProtocol {
    Foo(struct {
      // input param
      input uint32;
    }) -> (struct {
      // output param
      output uint32;
    });
};

結束日期:

type FooRequest = struct {
  // input param
  input uint32;
};

type FooResponse = struct {
  // output param
  output uint32;
};

protocol MyProtocol {
    Foo(FooRequest) -> (FooResponse);
}

沒有 API 或 ABI 的影響 (假設 FooRequestFooResponse 由編譯器保留的名稱)。

繫結

繫結的主要影響是,API 與一組要求/回應參數組合的對應形式都會經過簡化,或是不對 視要求或回應的頂層類型而定。目前所在的位置 已簡化與未整併產生的 API 的例項。

這裡有個「扁平化」API 指的是使用要求 直接替換回應參數 以及結構體例如, 調整為 FIDL 方法 GetName(struct { id uint32; }) -> (struct { name string; }) 是:void GetName(uint32_t id, GetNameCallback callback)。 FIDL 中指定的參數會直接對應到 C++ 中的函式參數。

「非整併」API 是指頂層類型本身 向使用者呈現的資訊在上一個範例中,這看起來會像這樣: void GetName(GetNameRequest req, GetNameCallback callback)GetNameRequest 對應於頂層結構體類型,且會有一個 uint32 id ] 欄位。

在目前的語法中,所有頂層要求或回應類型都來自 隱含結構,簡化參數,讓參數直接對應 我們接受函式簽章的引數,因為新增或移除 struct 成員無論如何都與 ABI 和 API 不相容 (即 產生的繫結不會對保證 由 FIDL 提供)。不過,並非如此,例如資料表和聯集 這項服務支援新增及移除成員因此,在某些情況下 因為在支援語言與測試語言的相容性保證下, 建構用於表示方法的建構 (在此範例中為位置 C++ 中的函式引數) 的限制比頂端實體引數更加嚴格 層級類型 (例如資料表或聯集)以上述範例再次說明 都代表GetName(table { 1: id uint32; }) -> (table { 1: name string;}) 您需要產生形式為 void GetName(GetNameRequest req, GetNameCallback callback) 的非整形簽章,才能維護 資料表頂層類型提供的相容性保證。

對於產生的函式或方法,Dart 等部分程式設計語言 方法是在傳送要求中使用具名引數 但由於原因在於 也不必在接收方法中加入新參數

總結來說,使用扁平化 API 做為結構的繫結程式碼可能需要 則可提供不同的非扁平化 API (如果頂層類型為資料表或 聯集。繫結目前已產生未整併的 API: 例如,在 LLCPP 中的 MyProtocol::MyRequestMyProtocol::MyResponse, 在頂層結構體的 API 之間沒有如此差異 要求/回應,或頂層聯集或資料表要求/回應。

JSON IR

系統會變更 maybe_requestmaybe_response 的 JSON 項目。舊 結構定義:

"maybe_request": {
    "description": "Optional list of interface method request parameters",
    "type": "array",
    "items": {
        "$ref": "#/definitions/interface-method-parameter"
    }
},

會變成:

"maybe_request_payload": {
    "description": "Optional type of the request",
    "$ref": "#/definitions/compound-identifier"
},

(與 maybe_response 相同)

"maybe_request_payload"」欄位已存在與這個形狀相符,但 尚未在 JSON IR 中指定,這是「 訊息表示法」。實務上,JSON IR 變化 這個 RFC 需要完成從 "maybe_request""maybe_request_payload" (請參閱「導入」一節)。

實作

這個 RFC 的實作包含兩個部分:第一個是 修改所有現有檔案,使其符合新語法的外觀變更 ,第二部分將 FIDL 編譯器和繫結變更為 允許資料表和聯集做為頂層類型。語法變更將會 作為 RFC-0050 FIDL 語法的一部分,但支援 可以延遲處理聯集和資料表頂層類型,以免發生 無法使用 FIDL 語法改良專案的區塊所有寫入 「新增」語法都必須符合 RFC、 正式的 FIDL 文法也會更新以反映設計 RFC-0050 其餘的時間。

在某些情況下,現有的繫結會啟用頂層類型的 不需要大量列出要求和回應的表格和聯集 除了處理新的 JSON IR 格式之外,還其他功能。如果不是,例如 繫結中的編碼和解碼作業取決於 頂層型別是一個結構體,有兩種可能的方法:

  • 第一種方法是先將所有資料表和聯集納入結構中, 編碼和解碼作業這可能很不吸引人,因為如要使用這項功能, 並為編碼和解碼新增了額外步驟。
  • 另一種方法是修改編碼/解碼程式碼, 支援非結構體的輸入內容。目前已經有一些程式碼 假設輸入內容一律為結構 (例如 LLCPP 中的特徵只會針對結構體產生,以及要求和回應 Rust 中的編碼是透過元組 (而非結構體) 發生,但數字 才能實現這項假設 並掌握在兩者間做出的取捨 。這種方式除了方法呼叫以外, 因此,您就不需要包裝在永久性結構中的結構體類型 資料用途

JSON IR

https://fxbug.dev/42157011 中有 已將遷移作業進行中,以移動 "maybe_request""maybe_response" 欄位移出 JSON IR,因此只要 要求和回應類型只會在 FIDL 後端發生。這項作業已暫停 ,但將會繼續執行,以便實作此 RFC。 目前,C++ 後端是唯一會使用 "maybe_request""maybe_response" (但其他使用 JSON 的程式庫 FIDL 轉碼器等 IR 也需要更新。

安全性與隱私權

此 RFC 不會修改 FIDL 線格式,因此不會影響安全性 和隱私權

測試

這個 RFC 將會使用現有的基礎架構進行測試,包括單元測試、黃金級測試、 和整合測試 (例如 FIDL 相容性測試)。

說明文件

啟用這項功能後,應新增說明文件 (包括範例) 說明新功能

缺點、替代方案與不明

語法

此 RFC 中建議的語法會讓使用結構體做為 較為精簡的類型,因為需要明確指定。 其他選擇可能包括為常見用途介紹語法糖 (例如 保留結構體目前的語法,並針對 表格和聯集) 的特性,但無論在何種情況下,都較為明確 比降低詳細程度更重要

語法中另一個可能被視為無吸引力的部分 加在括號中:(struct { ... }),這個問題也曾討論到 FTP-058。首先說的是保持一致風格:請保留大括號 可確保要求內類型之語法與 則為 FIDL 檔案中其他種類的型別。為避開 FTP-058 所採取的方法 將多餘的大括號替換成空格 (例如 MyMethod struct { ... } -> union { ... };),或許也適用。在 FIDL 文字中,這個函式 樣式與提案的其餘部分一致 與 Rechsia 殼層中所用的 FIDL 更是以 C-family/Go 為基礎的語法。

最後,我們建議的另一個替代方案是變更語法 用於指定類型,以便與方法參數語法對齊:結構體會是 方法是使用元組/記錄 (例如語法):type MyStruct = (foo Foo, bar Bar);。這樣一來,我們就可以在頂層 level 類型是藉由省略額外的括號 MyMethod(foo Foo, bar Bar); 的結構。以下舉例說明,建議內容如下:

// Declare a struct with two fields foo, bar.
type SomeStruct = (foo Foo, bar Bar);

protocol MyProtocol {
  // Declare a method with two request parameters.
  // The two parameters are stored in a struct.
  MyStructMethod(foo Foo, bar Bar);

  // Declare a method with two optional parameters.
  // The two parameters are stored in a table.
  MyTableMethod table { 1: foo Foo, 2: bar Bar };
};

繫結

設計中所述,在許多情況下,繫結無法壓平或 在產生的資料表和聯集產生的 API 中,內嵌頂層類型的成員 與 struct 的類似,以免引入 相容性限制適用於方法頂層類型成員時機的規則 內嵌生成式 AI 或不容易記住 需要依賴文件或產生的程式碼檢查 決定每個 FIDL 通訊協定方法的最終 API 為何。這個 會造成一些複雜情況,也就是 繫結 API 持續內嵌/簡化頂層類型成員。

理論上,您可以透過不分割要求的方式來提供一致的 API 但實際上這是不可行的 必須遷移所有依賴於這個 API 的使用者程式碼執行個體 ( 通常是與 FIDL 方法互動的使用者程式碼)。

既有藝術與參考資料

此 RFC 中建議的語法較接近 gRPC 中使用的語法,其中 方法 就是用單一 protobuf 訊息指定回應與回應。

ctiller@google.com 之前曾建議過與此 RFC 類似的想法, 允許使用序數語法 (例如 MyMethod(1: foo Foo; 2: bar Bar)) 暗示 頂層類型是資料表而非結構體主要差別在於 此 RFC 支援頂層聯集以及表格和結構體, 因為聯集也會使用 有些奇蹟