| RFC-0023:通訊協定的組合模型 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 關鍵字介面已由關鍵字通訊協定取代。擴充通訊協定:明確指出組合模型,其中一個通訊協定可定義為一組訊息,並由一或多個其他通訊協定擴增。通訊協定擴充功能使用的語法已從類似於繼承的語法,變更為類似於 mixin 的語法。 |
| 作者 | |
| 提交日期 (年-月-日) | 2018-12-10 |
| 審查日期 (年-月-日) | 2019-01-09 |
摘要
我們建議進行下列變更:
- 關鍵字 interface 已替換為關鍵字 protocol。 (本文其餘部分將使用「通訊協定」一詞)。
- 擴充通訊協定會明確指出組合模型,其中一個通訊協定可定義為一組訊息,並由一或多個其他通訊協定擴充。
- 通訊協定擴充功能使用的語法已從類似於「繼承」的語法,變更為類似於「混入」的語法。
- 在目標語言中表示組合式通訊協定時,繫結作者必須避免歸類 (例如「is-a」階層、繼承、子型別)。
提振精神
「介面」一詞的背景脈絡包括方法多載、建構函式和解構函式、做為訊息接收者的物件模型等。
不過,FIDL 的目標較為簡單,旨在說明兩個對等互連裝置之間的通訊協定,也就是可交換的一組訊息。
我們從一開始就明確指出 FIDL API 的這一點,例如:「雖然語法類似於物件導向介面的定義,但設計考量因素更接近網路通訊協定,而非物件系統。」當面臨導入更多「物件導向式」功能的選項時,我們避開了這類功能 (例如最近在有關 RFC-0020:序數雜湊的留言中)。
我們希望語言中的差異更明確,建議您將關鍵字 interface 替換為關鍵字 protocol,藉此變更語法。
此外,借用繼承語法所隱含的「is-a」關係並不健全,會導致錯誤的預期結果。(為求明確,FIDL 不提供這類繼承語意,但語法會建議這麼做)。詳情請參閱「『Is A』關係是否視為有害」一節。
設計
這項提案引進正式語意,用於說明程序互動和通訊協定。
這項提案會變更 FIDL 來源語言,以釐清通訊協定擴充功能的語意,並為繫結作者提供新指引。
目前 JSON IR 中不會顯示繼承關係,因此繫結作者無法運用這項資訊。因此,除了改善文件外,我們預期這項新指引對產生的繫結程式碼影響不大。
這項提案不會變更線路格式。
這項提案不會變更 JSON IR,但我們預計會在日後進行大規模變更時,一併重新命名金鑰。
通訊協定模型
Zircon 管道不需要特定結構定義來傳輸酬載。FIDL 以這個基本項目為基礎,並限制管道傳輸特定通訊協定。這樣一來,FIDL 就能為管道兩端賦予意義和名稱。我們將其中一個稱為「用戶端」,另一個稱為「伺服器」。
我們的模型將「通訊協定」描述為「一組導向互動」,並可選擇性地加入「墓誌銘」。我們將用戶端與伺服器之間使用通訊協定的特定通訊執行個體稱為「工作階段」。
方向可以是從用戶端到伺服器,也可以是從伺服器到用戶端。
互動會從要求開始,並視需要提供回應。 我們通常會使用「fire and forget」或「one way」一詞表示沒有回應的互動,並使用「call」一詞表示預期會有回應的要求。
要求和回應都是訊息,會以標頭表示,後面接著結構體的酬載,也就是要求或回應的引數。
目前,我們限制伺服器到用戶端訊息不得有回覆。 簡單來說,「事件啟動後便不須控制」。
墓誌銘是伺服器與用戶端之間的互動,用於結束工作階段。詳情請參閱 RFC-0053:墓誌銘。
這個模型不包含更複雜的互動,例如 TCP 的三向握手 (SYN/SYN-ACK/ACK)。我們認為這超出範圍,且模型日後不太可能涵蓋這類內容。
組合模型
如今,通訊協定既可定義互動,也能擴充一或多個通訊協定。產生的通訊協定 (即「組合通訊協定」) 會直接定義所有互動,並沿用其前身 (直接或間接) 定義的所有互動。
舉例來說,Child 通訊協定定義如下:
protocol Parent1 { Method1(); };
protocol Parent2 { Method2(); };
protocol Child { compose Parent1; compose Parent 2; Method3(); };
會包含所有三種互動:Method1、Method2 和 Method3。
不過,Method1 和 Method2 是在 Child 中定義為組合結果,還是直接定義,不會傳送至語言專屬後端,也就是說,這不會以 JSON IR 表示。
「Is A」關係經認定有害
由於通訊協定可以雙向傳送要求,因此建立子型別關係時需要更加謹慎。因此,我們不允許通訊協定與其擴充的通訊協定具有「是」關係。
舉例來說,假設我們有以下兩個通訊協定:
protocol Parent { Method(); };
protocol Child { ->Event(...); };
如果我們允許將攜帶通訊協定 Child 的管道視為 Parent (即「Child 是 Parent」關係),用戶端就會收到無法處理的 Event。如需具體範例,請參閱下一節。
我們將支援「僅限用戶端與伺服器互動」等特定通訊協定註解,以支援及允許「是」關係。發生這種情況時,這類關係會傳送至 JSON IR,供後端使用。
目前依賴「Is A」關係
以具體範例來說,fuchsia.media 程式庫會將各種通訊協定組合在一起。 請特別注意以下幾點:
AudioCapturer 和 AudioRenderer 都不會定義事件,也就是說,這些純粹是「用戶端對伺服器通訊協定」,屬於單向通訊。(StreamSource 定義了兩個事件,但我們在此特別討論各個通訊協定本身的定義)。
因此,如果用戶端知道如何與 StreamBufferSet 或 StreamSource (分別為 StreamBufferSet 或 StreamSink) 互動,也可以與 AudioCapturer (和 AudioRenderer) 互動,也就是說,用戶端只會忽略公開的額外方法。在這裡,我們可以如預期定義「is a」關係。
不過,如果要在任一介面中新增事件,這種「是」的」關係就會消失。假設用戶端與 StreamBufferSet 互動,但實際上是伺服器端的 AudioRenderer。如果 AudioRenderer 觸發事件,會發生什麼情況? 該客戶會如何處理?
由於我們 (目前) 無法在 fidlc 中提供這項區別,因此我們確認不支援「是」關係。這項提案基本上是為了釐清現狀。
如同 fuchsia.media 案例,瞭解特定關係為真的作者可以根據需求調整繫結 (使用轉型等)。
在後續提案中,我們預計會導入屬性或新關鍵字,以擷取這個方向性限制,並根據此限制在繫結中提供「是」關係。在此之前,我們無法透過 FIDL 工具鍊提供更完善的支援。
語法變更
使用可接受語法的擴充通訊協定如下所示:
protocol Parent1 {
Method1OfParent1();
Method2OfParent1();
};
protocol Parent2 {
Method1OfParent2();
Method2OfParent2();
};
protocol Child {
compose Parent1;
compose Parent2;
Method1OfChild();
Method2OfChild();
};
正式來說,文法變更如下:
declaration = const-declaration | enum-declaration | protocol-declaration |
struct-declaration | union-declaration | table-declaration ;
protocol-declaration = ( attribute-list ) , "protocol" , IDENTIFIER ,
"{" , ( method-or-compose-declaration , ";" )* , "}";
method-or-compose = method-declaration | compose-declaration ;
method-declaration = ( ordinal , ":" ) , method-parameters ;
method-parameters = IDENTIFIER , parameter-list , ( "->" , parameter-list )
| "->" , IDENTIFIER , parameter-list ;
compose-declaration = "compose", compound-identifier ;
組合通訊協定只能提及一次。
可能延期
我們預期後續提案會額外允許伺服器與用戶端互動,因此可讓管道上的多工通訊協定 (可能以相反順序) 要求回應。舉例來說,coordinator.fidl 定義了兩個指令回應通訊協定,一個是從 devmgr -> devhost,另一個是從 devhost -> devmgr。目前這些項目是手動多工處理,並依賴序數調度來分類。
我們可能會在撰寫區塊中使用「->」語法,以便稍後以反向方式導入多工處理。 另一種做法是,只有在擴充功能包含反向通訊協定時,才需要明確指示,這樣做的好處是不會導入任何方向語法,因為我們正在延後推出包含反向通訊協定的擴充功能。
我們允許將組合區塊放在通訊協定的定義中,也允許使用多個組合區塊。我們也可以只使用一個區塊,並要求這個區塊位於頂端。 我們選擇開放,並依賴自動格式和/或樣式指南來提供建議,而不是將強制執行功能內建於語言本身。
JSON IR
我們不會在這次異動中變更 JSON IR。
我們不會移除「interface_declarations」鍵,而是會將其重新命名為「protocol_declarations」,這是大規模變更的一部分。這項重大變更需要分階段進行,將結構定義版本從 0.0.1 升級至 0.0.2,並為後端提供適應期。
遠距中斷和 [FragileBase] 的使用
這項提案不會改變遠距離中斷的可能性,因此我們重申,任何擴充通訊協定都應使用 [FragileBase]1。
繫結作者指南
- 在目標語言中表示組合通訊協定時,繫結必須避免涵蓋 (例如「is-a」階層、繼承、子型別)。
- 應該會收到不明序數的錯誤。 繫結應將此錯誤泡泡化為「不明序數錯誤」,並關閉管道。
導入策略
三個步驟:
- 新增對新語法的支援;
- 將所有 FIDL 檔案轉換為使用新語法;
- 停止支援舊語法。
人體工學
這項變更可讓 FIDL 更容易理解,請參閱動機一節。 這項變更可能無法讓您立即瞭解 FIDL,但可避免日後產生誤解和預期不符的情況。
說明文件和範例
我們需要更新語言、文法、評分量表和其他相關文件。
回溯相容性
這項變更會破壞目前使用繼承的 FIDL 檔案的來源相容性。如實作所述,我們會分階段導入新語法、遷移所有 FIDL 檔案,然後移除舊語法的支援。
這項變更不會變更 FIDL 傳輸格式,因此是回溯相容的 ABI 變更。
效能
不會影響效能。
安全性
我們可能會利用更嚴格的型別語意來保護或觀察管道。這並非提案目標,也不會使現狀倒退,甚至可說是改善現狀。
測試
這項變更的測試完全可以在 fidlc 層級進行單元測試。
缺點、替代方案和未知事項
以下各節記錄設計階段提出的替代語法。
替代語法 (pascallouis@)
範例:
protocol Parent1 { Method1(); };
protocol Parent2 { Method2(); };
protocol Child {
compose {
-> Parent1();
-> Parent2();
};
Method1OfChild();
}
附註:這是原始建議的語法。
compose 區塊似乎不自然,而且與現有語言的差異過大。
這讓撰寫多個通訊協定成為偏好的方法,而撰寫單一通訊協定則顯得冗長。此外,我們也不清楚是否允許多個 compose 區塊,以及會是什麼樣子。最後,我們選擇不為偏好引入多向多工的通訊協定提供「->」方向指標 (如果考慮這類功能)。
替代語法 (jeremymanson@)
原因:釐清我們預期實作的方法清單,以及定義通訊協定的方法清單之間的差異:
範例:
protocol Parent1 {
Method1OfParent1();
Method2OfParent1();
};
protocol Parent2 {
Method1OfParent2();
Method2OfParent2();
};
interface Child {
compose {
-> Parent1();
-> Parent2();
};
Method1OfChild();
};
附註:「interface」關鍵字表示每個方法都必須有實作項目,「protocol」關鍵字則表示符合通訊協定和介面的需求,並納入其中。舉例來說,我們不一定會預期 StreamSource 會有自己的實作項目。
這項做法明確指出不會發生實作繼承,因此我們離實作繼承更遠。您無法將介面組合到另一個介面中。
替代語法:類似 Go 的介面組合 (proppy@)
原因:不像是繼承,熟悉介面嵌入的 Golang 語法
範例:
protocol Parent1 { Method1(); };
protocol Parent2 { Method2(); };
protocol Child {
Parent1;
Parent2;
Method3();
};
附註:Go 語言規格介面和嵌入。
替代語法:使用宣告 (jeffbrown@)
原因:看起來不像繼承,重複使用現有關鍵字來表示帶入範圍的名稱。較不容易與方法宣告或「property2」混淆。
範例:
protocol Parent1 { Method1(); };
protocol Parent2 { Method2(); };
protocol Child {
using Parent1;
using Parent2;
Method3();
};
附註:FIDL、C++、Rust 和其他語言的先例。
替代關鍵字
「compose」關鍵字的替代方案:
extends(pascallouis@)contains(smklein@)
既有技術和參考資料
沒有特定內容。
Cap'n Proto 具有支援多重繼承 (採用 Mixin 樣式) 的介面。