RFC-0050:FIDL 語法重整 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 我們會建立語法選擇的指導原則,並根據這些原則進行一些語法變更。 |
作者 | |
提交日期 (年-月-日) | 2020-02-26 |
審查日期 (年-月-日) | 2019-06-04 |
摘要
我們會建立語法選擇的指導原則,並根據這些原則進行一些語法變更。
變更
- 將類型放在第二個位置,例如在方法參數名稱前方加上各自的類型,在表格宣告中成員名稱前方加上各自的類型;
- 變更類型,將版面配置與限制分開,將版面配置相關類型資訊放在
:
分隔符左側,而限制資訊則放在右側,例如array<T, 5>
與vector<T>:5
更清楚地表示陣列的大小會影響版面配置,而這項限制是向量。 - 推出匿名版面配置。例如,
table { f1 uint8; f2 uint16; }
可直接用於方法參數清單中。 - 頂層類型的宣告是透過使用匿名版面配置,並利用
type Name = Layout;
表單的類型引入宣告來完成。 - 最後,針對通訊協定
P
,重新命名P
和request<P>
為client_end:P
和server_end:P
。請注意,此通訊協定是用戶端或伺服器端的限制條件,而非先前的位址,因為後者會錯誤指出版面配置相關問題。
與其他 RFC 的關係
- 這個 RFC 包含 RFC-0038:將版面配置與限制條件分開 和 RFC-0039:類型為次要,也就是說,接受 RFC-0050 就表示將 RFC-0038 和 RFC-0039 視為「已淘汰」。
- 本 RFC 提出了 RFC-0044:可擴充的方法引數的替代解決方案,也就是說,接受 RFC-0050 就表示將 RFC-0044 視為「已淘汰」。
這份 RFC 後來經過以下修訂:
- RFC-0086:更新 RFC-0050:FDil 屬性語法
- RFC-0087:更新 RFC-0050:FDil 方法參數語法
- RFC-0088:更新 RFC-0050:FIDL 位元、列舉和限制語法
提振精神
入門範例
代數資料類型
語法相當多元,可流暢地表示代數資料類型 (ADT),無須額外使用任何糖衣。舉例來說:
/// Describes simple algebraic expressions.
type Expression = flexible union {
1: value int64;
2: bin_op struct {
op flexible enum {
ADD = 1;
MUL = 2;
DIV = 3;
};
left_exp Expression;
right_exp Expression;
};
3: un_op struct {
op flexible enum {
NEG = 1;
};
exp Expression;
};
};
在模式方面,我們選擇使用 struct
的 union
:union
提供可擴充性,因此不需要 (也不建議) 使用較嚴格的變化版本。如果需要變更變化版本,我們可以改為新增整批新變化版本,並改用這些新變化版本。(在需要可發展性的其他位置,例如二元或一元運算子的清單,則會選擇彈性列舉)。
除了人體工學語法來描述資料類型,支援 ADT 還需要更多內容。舉例來說,其中一個預期的關鍵功能就是輕鬆建構和銷毀 (例如透過模式比對或檢視器模式)。
這份 RFC 不會為 FIDL 引入新功能,而遞迴型別的限制會導致範例無法在今天編譯。我們計劃新增對一般化遞迴型別的支援,而這個擴充功能將成為日後 RFC 的對象。
更輕鬆地將不可進化的訊息與可進化訊息結合
舉例來說,您可以表示「可擴充的結構體」,其中包含結構體元素 (精簡、內嵌、快速編碼/解碼),以及可擴充的可能性:
type Something = struct {
...
/// Provide extension point, initially empty.
extension table {};
};
舉例來說,fuchsia.test.breakpoints
程式庫需要定義名為 Invocation
的可擴充事件。這些事件都會共用通用值,以及事件的每個變化版本專屬的酬載。這項作業現在可以更直接且簡潔地表達為:
type Invocation = table {
1: target_moniker string:MAX_MONIKER_LENGTH;
2: handler Handler;
3: payload InvocationPayload;
};
type InvocationPayload = union {
1: start_instance struct{};
2: routing table {
1: protocol RoutingProtocol;
2: capability_id string:MAX_CAPABILITY_ID_LENGTH;
3: source CapabilitySource;
};
};
可擴充的方法引數
舉例來說,可擴充的方法引數:
protocol Peripheral {
StartAdvertising(table {
1: data AdvertisingData;
2: scan_response AdvertisingData;
3: mode_hint AdvertisingModeHint;
4: connectable bool;
5: handle server_end:AdvertisingHandle;
}) -> () error PeripheralError;
};
使用 table
做為引數並非「最佳做法」。這可能很適合,但會帶來一連串問題,例如 N 個欄位有 2N 種可能性,可能會讓收件者變得非常複雜。
指導原則
FIDL 主要用於定義 Application Binary Interface (ABI) 關切項目,其次則是應用程式設計介面 (API) 關切項目。這可能會導致語法比一般人習慣或預期的程式語言更冗長。舉例來說,集合的 unit
變化版本會以空結構表示,如上述 InvocationPayload
範例所示。我們可以選擇引入語法糖來省略這類型別,但這會違反將 ABI 疑慮置於首要位置的做法。
將版面配置與限制條件分開
對齊語法
layout:constraint
對於類型,也就是控制版面配置的任何項目都會放在冒號之前,控制限制的項目則會放在冒號之後。版面配置會說明位元組的排版方式,以及解讀方式。限制會限制在版面配置下可顯示的內容,這是在編碼/解碼期間執行的驗證步驟。
這個語法可讓您以簡單的方式考量變更的 ABI 影響,特別是可導出兩個簡寫規則:
- 如果兩種型別的版面配置不同,就無法從一種型別轉換為另一種型別,反之亦然 1,也就是說變更左側會破壞 ABI
- 限制條件可以演進,只要寫入器的限制條件比讀取器多,兩者就會相容,也就是說可以演進右側並保留 ABI
以下是遵循這項原則的變更範例:
array<T>:N
變成array<T, N>
handle<K>
變成handle:K
vector<T>?
變成vector<T>:optional
Struct?
變成box<Struct>
Table?
變成Table:optional
Union?
變成Union:optional
先使用二進位線格式
雖然許多格式都能代表 FIDL 訊息,但 FIDL 線路格式 (或稱「FIDL 二進位元線路格式」) 是優先處理的格式,系統會優先處理這類格式。
也就是說,如果您選擇的語法能讓語法一致性與 ABI 一致,就應考慮以二進位線格格式下的 ABI 為準 (而非 JSON 等其他格式)。
舉例來說,類型的 ABI 名稱並無影響,但協定和方法的名稱則有影響。雖然名稱可能會影響可能的 JSON 格式,但我們在選擇語法時會偏向二進位 ABI 格式,且不會為了文字表示而變更語法,以免影響對 ABI 規則的理解。
最少功能
根據 Wright 的「形式和功能應為一體」的說法,我們會盡力讓外觀相似的結構體具有相似的含義,反之亦然。舉例來說,所有內部運用信封的可擴充資料,一律會以 ordinal:
呈現。
layout {
ordinal: name type;
};
我們致力於減少功能和規則的數量,並希望能整合功能以實現用途。實際上,在考慮新功能時,我們應先嘗試調整或推廣其他現有功能,而非推出新功能。舉例來說,雖然可為可擴充的方法引數 (和傳回值) 設計特殊語法,如 RFC-0044:可擴充的方法引數所述,但我們建議使用 table
和一般語法。
有人認為,我們甚至應該為方法要求和回應要求匿名的 struct
版面配置,而不是從大多數程式設計語言借來的引數現有語法糖果。不過,另一個設計考量是協助程式庫作者在匯總中達成一致性:在 enum
版面配置宣告中,我們偏好使用語法糖,而非明確選擇包裝類型,因為擁有合理的預設值,可為 FIDL 程式庫中的列舉提供更一致的效果。這反過來提供遷移路徑,以便日後切換列舉,例如程式庫應定義一般用途的 ErrorStatus
列舉,日後可由其他「更佳」的一般用途 ErrorStatusV2
取代。
設計
類型
類型採用一般格式:
Name<Param1, Param2, ...>:<Constraint1, Constraint2, ...>
空值類型參數化必須省略 <
和 >
,也就是 uint32
(而非 uint32<>
)。
沒有限制的類型必須省略 :
分隔符、<
、>
,也就是 uint32
(不是 uint32:<>
或 uint32:
)。
具有單一限制的類型可以省略 <
和 >
,也就是說,vector<uint32>:5
和 vector<uint32>:<5>
都允許且等同於。
內建
系統支援下列基本類型:
- 布林值
bool
- 帶符號整數
int8
、int16
、int32
、int64
- 無號整數
uint8
、uint16
、uint32
、uint64
- IEEE 754 浮點
float32
、float64
固定大小的重複值:
array<T, N>
這可以視為 struct
,其中包含 N
元素,類型為 T
。
重複值的大小不固定:
vector<T>
vector<T>:N
也就是說,可以省略大小 N
。
可變大小的 UTF-8 字串:
string
string:N
也就是說,可以省略大小 N
。
核心物件參照,也就是句柄:
handle
handle:S
其中子類型 S
為 bti
、buffer
、channel
、debuglog
、event
、eventpair
、exception
、fifo
、guest
、interrupt
、iommu
、job
、pager
、pcidevice
、pmt
、port
、process
、profile
、resource
、socket
、suspendtoken
、thread
、timer
、vcpu
、vmar
、vmo
之一。
RFC-0028:句柄權利中引入的權利句柄:
handle:<S, R>
其中權利 R
為權利值或權利運算式。
通訊協定物件的參照,也就是指定用途的管道句柄:
client_end:P
server_end:P
例如 client_end:fuchsia.media.AudioCore
或 server_end:fuchsia.ui.scenic.Session
。
具體來說,單獨參照通訊協定是不合法的:通訊協定宣告不會引入類型,只能視為一種用於用戶端或伺服器端的類型。詳情請參閱「傳輸通用化」一節。
版面配置
除了內建版面配置外,我們還有五種版面配置可設定為引入新類型:
enum
bits
struct
table
union
有限版面配置
enum
和 bits
版面配置的表示方式相似:
layout : WrappedType {
MEMBER = expression;
...;
};
其中 : WrappedType
為選用項目 [^2],如果省略,則預設為 uint32
。
enum
範例:
enum {
OTHER = 1;
AUDIO = 2;
VIDEO = 3;
...
};
bits
範例:
bits : uint64 {
TOTAL_BYTES = 0x1;
USED_BYTES = 0x2;
TOTAL_NODES = 0x4;
...
};
彈性版面配置
table
和 union
版面配置的表示方式相似:
layout {
ordinal: member_name type;
...;
};
在此,ordinal:
可視為描述 envelope<type>
的語法糖。
在資料表中,成員通常稱為欄位。對於聯集,成員通常稱為變數。此外,成員可能會保留:
layout {
ordinal: reserved;
...
};
剛性版面配置
唯一的固定版面配置 struct
是以接近彈性版面配置的方式表示,但沒有彈性符號:
layout {
member_name type;
...;
};
就結構體而言,成員通常稱為欄位。
屬性
版面配置前方可能會加上該版面配置的屬性:
[MaxBytes = "64"] struct {
x uint32;
y uint32;
};
這樣一來,您就能明確地將屬性附加至版面配置的成員和該成員的類型:
table {
[OnMember = "origin"]
1: origin [OnLayout] struct {
x uint32;
y uint32;
};
};
在引入新類型 (即版面配置) 的情況下,新引入類型的屬性有兩種可能的放置位置:
- 在新的類型:
[Attr] type MyStruct = struct { ... }
。 - 版面配置:
type MyStruct = [Attr] struct { ... }
。
fidlc
會將這些屬性視為等效,如果在兩個位置都指定屬性,系統會擲回錯誤。
無論使用哪個刊登位置指定屬性,屬性在概念上都會附加至版面配置本身,而非整個類型段落。這項功能的實際應用範例是,在任何 IR 中,偏好設定會將類型段落的屬性降至版面配置,而非將版面配置的屬性提升至類型段落。
命名內容和版面配置用法
版面配置本身不含名稱,因此所有版面配置都是「匿名」的。而是使用特定版面配置,以決定目標語言中的名稱。
舉例來說,版面配置最常見的用途是引入新的頂層類型:
library fuchsia.mem;
type Buffer = struct {
vmo handle:vmo;
size uint64;
};
在本例中,頂層程式庫中的「new type」宣告會使用結構體版面配置。
在 Express 可擴充方法引數的介紹說明中,我們已介紹匿名內容中的使用範例:
library fuchsia.bluetooth.le;
protocol Peripheral {
StartAdvertising(table {
1: data AdvertisingData;
2: scan_response AdvertisingData;
3: mode_hint AdvertisingModeHint;
4: connectable bool;
5: handle server_end:AdvertisingHandle;
}) -> () error PeripheralError;
};
在本例中,表格版面配置會在 Peripheral
通訊協定宣告中,於 StartAdvertising
方法的要求中使用。
我們會參照名稱清單,從最不具體到最具體,藉此將版面配置的用途視為「命名內容」。在上述兩個範例中,我們分別使用 fuchsia.mem/Buffer
和 fuchsia.bluetooth.le/Peripheral, StartAdvertising, request
做為兩個命名參照。
在 JSON IR 中,版面配置宣告會包含命名結構,也就是上述所述的名稱階層清單。
命名慣例
在程式庫 some.library
中,type Name =
宣告會為 some.library/Name
引入命名內容。
在 Protocol
中使用 Method
的請求 (分別為回應) 會引入 some.library/Protocol, Method,
request/response
的命名背景
在版面配置中使用時,會將欄位名稱 (或變化版本名稱) 新增至命名內容。例如:
type Outer = struct {
inner struct {
...
};
};
第一個外部結構版面配置的命名結構為 some.library/Outer
,第二個內部結構版面配置的命名結構為 some.library/Outer, inner
。
產生的扁平化名稱
許多目標語言都能以階層方式呈現命名結構。舉例來說,在 C++ 中,您可以在包函型別內定義型別。不過,某些目標語言不具備這項能力,因此我們必須考慮因扁平化命名內容而造成的名稱衝突。
舉例來說,請考慮命名內容 some.library/Protocol, Method,
request
。這可能會在 Go 中扁平化為 some.library/MethodRequestOfProtocool
。如果其他定義恰好使用命名結構 some.library/MethodRequestOfProtocool
,那麼 Go 繫結就會面臨難題:必須重新命名兩個宣告中的其中一個。最糟的情況是,如果程式庫從只有一個宣告 (沒有名稱衝突) 演變為有兩個宣告 (有名稱衝突),則 Go 繫結必須與先前產生的內容一致,以免來源發生重大變更。
根據我們的經驗,這些決策最好交由核心 FIDL 編譯器處理,而非將工具鍊委派給 FIDL 繫結。因此,我們會計算並保證穩定的扁平化名稱。
在 JSON IR 中,命名內容會包含產生的扁平化名稱,而編譯器會保證該名稱在全域範圍內是唯一的,也就是前端編譯器負責產生扁平化名稱,並驗證扁平化名稱不會與其他宣告 (可能是其他扁平化名稱或頂層宣告) 衝突。
以上述範例為例,如果程式庫作者新增的宣告 type
MethodRequestOfProtocool = ...
與另一個宣告產生的扁平名稱相衝突,編譯作業就會失敗。
繫結使用命名內容
綁定可大致分為兩類:
- 能夠在目標語言中表示命名內容範圍,例如 C++ 語言的繫結。
- 無法代表命名背景資訊,並改為使用產生的扁平化 nuse 扁平化名稱,例如 Go 語言的繫結。
這項做法相較於目前的情況有所改善,因為至少可以確保繫結之間的一致性,並在前端提供編譯器協助。目前,我們必須在遊戲後期 (後端) 產生部分名稱,這項做法既危險又容易出錯。
舉例來說,請考量以下定義:
type BinOp = union {
add struct {
left uint32;
right uint32;
};
};
在 C++ 繫結中,我們可能會:
class BinOp {
class Add {
...
};
};
變體 add
的存取子會是:
BinOp.add();
不會與類別定義衝突。
或者,在 Go 中使用扁平化名稱:
type BinOp struct { ... };
type BinOpAdd struct { ... };
如果程式庫作者日後決定引入名為 BinOpAdd
的頂層宣告,前端編譯器會偵測到這項操作,並將其回報為錯誤。程式庫作者可自行評估這項變更的影響,並決定是否要為了導入這項新宣告而破壞來源相容性。再次強調,這項功能相較於現況有進步,因為現況是這類來源相容性問題會在日後才發現,且與做出決策的時間相距較遠。
類型別名和新類型
在 RFC-0052:類型別名和新類型 中,我們改良了類型別名和新類型宣告。
別名的宣告方式如下:
alias NewName = AliasedType;
也就是說,與 RFC-0052 中提議的語法相同。
新類型宣告方式如下:
type NewType = WrappedType;
也就是說,無論包裝的型別是另一個現有型別 (包裝) 或某種版面配置 (新的頂層型別),新型別的語法都相同。這與 RFC-0052 中最初提出的語法不同。
選填
某些類型本質上可視為選用:vectors
、strings
、envelopes
,以及使用這類結構的版面配置,例如 table
(信封的向量) 和 union
(標記加上信封)。因此,這些類型是否為選用型是一種限制,可透過放寬限制變成可為空值,或透過收緊限制變成必填。
另一方面,int8
或 struct
版面配置等類型並非本質上可選的類型。為了提供選用性,您需要引入間接參照,例如在結構體案例中使用間接參照。因此,與本質上為選用的類型不同,這類型無法提供演進路徑。
為了區分這兩種情況,並遵循「將 ABI 疑慮放在左側」和「將可進化的疑慮放在右側」的原則,請執行以下操作:
自然是選用 | 不是自然的選項 |
---|---|
string:optional |
box<struct> |
vector:optional |
|
union:optional |
在命名方面,我們建議使用「optional」、「required」、「present」、「absent」等字詞。(我們應避免使用「nullable」、「not nullable」、「null fields」等字詞)。為了符合這個命名偏好設定,我們選擇 box<T>
而非 pointer<T>
。box
是預設的選用結構,也就是說,新語法的 box<struct>
等同於舊語法的 struct?
,而 box<struct>:optional
是多餘的,可能會觸發編譯器或 Linter 的警告。這麼做是為了更符合我們預期的用途:使用者通常會將結構體裝箱,以便取得選用性,而不是新增間接參照。
常數
常數的宣告方式如下:
const NAME type = expression;
限制順序
根據版面配置和限制條件對型別進行參數化時,這些引數的順序會固定為特定型別。這份 RFC 定義了以下限制順序 (目前沒有類型具有多個版面配置引數):
- 處理:子類型、權利、選用性。
- Protocol client/server_end:通訊協定、選用性。
- 向量:大小、選用性。
- 聯集:選填。
做為指導原則,選用性一律會放在最後,而對於句柄,子類型會放在權限之前。
舉例來說,請考慮這個結構體,其中包含在成員上定義的所有可能限制:
type Foo = struct {
h1 zx.handle,
h2 zx.handle:optional,
h3 zx.handle:VMO,
h4 zx.handle:<VMO,optional>,
h5 zx.handle:<VMO,zx.READ>,
h6 zx.handle:<VMO,zx.READ,optional>,
p1 client_end:MyProtocol,
p2 client_end:<MyProtocol,optional>,
r1 server_end:P,
r2 server_end:<MyProtocol,optional>,
s1 MyStruct,
s2 box<MyStruct>,
u1 MyUnion,
u2 MyUnion:optional,
v1 vector<bool>,
v2 vector<bool>:optional,
v3 vector<bool>:16,
v4 vector<bool>:<16,optional>,
};
未來方向
除了針對目前功能的語法進行變更,我們也將為近期推出的功能設定方向。這裡的重點是預期的表現力和語法算繪 (而非精確的語意,這需要單獨的 RFC)。舉例來說,雖然我們會說明傳輸通用化,但不會討論各種棘手的設計問題 (例如可設定程度、JSON IR 中的表示法)。
本節也應以方向性內容閱讀,而非未來規格。隨著新功能的推出,系統會評估相應的語法,並確認這些功能的確切運作方式。
內容相關名稱解析
例如:
const A_OR_B MyBits = MyBits.A | MyBits.B;
可簡化為:
const A_OR_B MyBits = A | B;
例如:
zx.handle:<zx.VMO, zx.rights.READ_ONLY>
可簡化為:
zx.handle:<VMO, READ_ONLY>
限制
聲明網站限制
type CircleCoordinates = struct {
x int32;
y int32;
}:x^2 + y^2 < 100;
使用網站限制
type Small = struct {
content fuchsia.mem.Buffer:vmo.size < 1024;
};
獨立限制
constraint Circular : Coordinates {
x^2 + y^2 < 100
};
信封限制
表格和可擴充的聯集的語法會隱藏信封的使用方式:
table
是vector<envelope<...>>
,且union
是struct { tag uint64; variant envelope<...>; }
。
目前,只有 table
和 union
宣告中出現的 ordinal:
會產生信封,因此您可以將這段語法視為信封的「糖衣」介紹。基本上,我們可以按照以下方式去除糖分:
去除糖衣的資料表和彈性聯集 | |
table ExampleTable { 1: name string; 2: size uint32; }; |
table ExampleTable { @1 name envelope |
union ExampleUnion { 1: name string; 2: size uint32; }; |
union ExampleUnion { @1 name envelope |
如果我們想限制 envelope
,例如將 require
設為元素,我們會將此限制條件放在序數 ordinal:C
上,例如:
去除糖衣的資料表和彈性聯集 | |
table ExampleTable { 1:C1 name string:C2; 2:C size uint32; }; |
table ExampleTable { @1 name envelope<string:C2>:C1; @2 size envelope |
union ExampleUnion { 1:C1 name string:C2; 2:C size uint32; }; |
union ExampleUnion { @1 name envelope<string:C2>:C1; @2 size envelope |
屬性
FIDL 的類型系統已經具備限制的概念。我們使用 vector<uint8>:8
表示向量最多有 8 個元素,或使用 string:optional
放寬選用限制,並允許字串為選用項目。
各種需求促使我們推出更具表達力的限制,以及針對如何統一及處理這些限制的觀點。
舉例來說,fuchsia.mem/Buffer 指出「這個大小不得大於 VMO 的實際大小」。我們正在進行RFC-0028:處理權限的相關作業,也就是限制處理手柄。或要求使用表格欄位的想法,也就是限制在選用信封上使用表格欄位。
目前無法描述所處理值或實體的執行階段屬性。雖然 string
值有大小,但無法命名。雖然 handle
具有相關權利,但也無法命名這些權利。
為了妥善解決與受限類型相關的表現力問題,我們必須先將值的執行階段面向與 Fidl 對這些值的有限檢視畫面連結起來。我們打算推出 **屬性 **,可視為附加至值的虛擬欄位。屬性不會影響線格格式,它們純粹是語言層級的建構,並會顯示在 JSON IR 中,以便繫結並賦予執行階段意義。屬性存在的唯一目的,就是表達對屬性的限制。每個屬性都必須在繫結中宣告,就像繫結會宣告內建項目一樣。
接著繼續上述範例,string
值可能會有 uint32 size
屬性,句柄可能會有 zx.rights rights
屬性。
例如:
layout name {
properties {
size uint32;
};
};
運輸一般化
宣告新傳輸機制至少需要定義新名稱、為傳輸機制支援的訊息指定限制 (例如「no handles」、「no tables」),以及為通訊協定指定限制 (例如僅限「fire-and-forget methods」、「no events」)。
預期的語法類似於以未指定類型的 FIDL 文字表示的設定:
transport ipc = {
methods: {
fire_and_forget: true,
request_response: true,
},
allowed_resources: [handle],
};
然後用於:
protocol SomeProtocol over zx.ipc {
...
};
處理一般化
目前,句柄是純粹的 Fuchsia 專屬概念:它們直接與 Zircon 核心相連,並對應至 zx_handle_t
(或 C 以外其他語言的等效項目),而其類型僅為核心公開的物件,例如 port
、vmo
、fifo
等。
在考量其他情況 (例如處理程序通訊) 時,一個理想的擴充點是能夠直接在 FIDL 中定義句柄,而非將其納入語言定義。
舉例來說,定義 zircon 句柄:
library zx;
resource handle : uint32 {
properties {
subtype handle_subtype;
rights rights;
};
};
type handle_subtype = enum {
PROCESS = 1;
THREAD = 2;
VMO = 3;
CHANNEL = 4;
};
type rights = bits {
READ = ...;
WRIE = ...;
};
這會允許 handle
或 handle:VMO
(或在另一個資料庫 zx.handle:zx.handle.VMO
中)。
實驗性實作項目已存在,並將用於中斷 Zircon 和 FIDL 之間的循環依附 (在這個變更之前,Zircon 的 API 是在 FIDL 中說明,但 FIDL 部分是根據 Zircon 的 API 定義)。
導入策略
系統會在所有 .fidl
檔案的頂端新增暫時的「版本宣告」,供 fidlc
使用,以便偵測 .fidl
檔案是否採用舊版或新版語法。
這個符號會緊接在程式庫陳述式之前:
// Copyright notice...
deprecated_syntax;
library fidl.test;
...
建議使用明確標記,以便簡化 fidlc
在偵測語法時的角色,並提高可讀性。檢測語法時遇到的挑戰之一,就是在解讀為任一語法時會導致編譯錯誤。在這些情況下,系統需要使用啟發式方法來決定使用舊版還是新版語法,這可能會導致意外的結果。
此外,這個符記會新增至先前語法的所有檔案,而非新增語法 (例如 new_syntax;"
),以便將即將進行的遷移作業公開化 - FIDL 檔案的讀者會知道語法即將變更,並可透過其他管道 (例如說明文件、電子郵件清單) 尋找其他背景資訊。
我們會新增新的 fidlconv
主機工具,可將舊格式的 FIDL 檔案轉換為新格式檔案,在本節中稱為 .fidl_new
。雖然這項工具與 fidlc
分開,但仍需要利用編譯器的內部表示法,才能正確執行此轉換。舉例來說,只有在 Foo
為通訊協定時,才需要將其轉換為 client_end:Foo
,以便判斷 fidlconv
是否會先利用 fidlc
編譯 FIDL 程式庫。
FIDL 前端編譯器 fidlc
以及格式化工具和 Linter 等隨附工具,將會根據上述定義的標記擴充,以支援上述任一語法。
有了這項新增功能,建構管道將會延伸如下:
也就是:
fidlconv
工具會將舊語法的 FIDL 檔案轉換為新語法。fidlc
編譯器會透過編譯舊語法輸出.json
。- 另外,
fidlc
編譯器會透過編譯新語法輸出.json
IR。 fidlfmt
格式化工具會格式化產生的新程式庫檔案.fidl_new
。
測試和驗證:
- 系統會比較兩個 JSON IR,並驗證是否相符 (除了跨度資訊)。
- 我們會驗證新程式庫檔案格式的冪等性,以便檢查
fidlc
編譯器和fidlfmt
格式化工具使用新語法的輸出內容。
在這個實作項目中,FIDL 團隊也會將編碼表後端移至獨立的二進位檔 (與其他後端相同),並透過產生最後一次使用情形,並在 fuchsia.git 樹狀結構存放區中檢查,淘汰並刪除 C 繫結後端。
人體工學
這份 RFC 將說明人體工學。
我們願意讓熟悉目前語法的開發人員在重新學習使用這個修改版語法時,短期內工作效率降低,因為我們堅信,未來使用 FIDL 的開發人員將因此大大受益。
說明文件和範例
您需要變更下列項目:
回溯相容性
這項變更不具備回朔相容性。請參閱轉換計畫的導入部分。
成效
這項變更不會影響成效。
安全性
這項變更不會影響安全性。
測試
請參閱實作部分的轉換計畫,並驗證其正確性。
缺點、替代方案和未知事項
使用半形冒號分隔名稱和類型
由於我們將類型移至第二個位置,因此也可以考慮使用相當常見的 :
分隔符,就像在型別理論、Rust、Kotlin、ML 語言 (SML、Haskell、OCaml)、Scala、Nim、Python、TypeScript 等許多語言中所做的那樣:
field: int32 rather than the proposed field int32
本提案拒絕採用這種做法。
:
分隔符主要用於將版面配置與約束條件分開。也用於表示 enum
和 bits
宣告的「包裝類型」。最後,它用於在 table
和 union
宣告中表示信封。進一步超載 :
分隔符,特別是在文法上與其主要用途相近的情況下,會導致混淆 (例如表格成員 1: name:
string:128;
)。
省略分號
我們已討論過如何省略結束宣告的半形逗號 (無論是成員、常數或其他)。
本提案選擇不探討這項簡化做法。
移除分號對 FIDL 作者來說,語法上並沒有太大差異。這也不是必要的變更,如果我們日後想探索這項功能,也能輕鬆修改 (例如 Go 移除分號的方法)。
不過,如果使用分號來結束成員和宣告,就能更輕鬆地確保語法規則不含歧義,尤其是在我們探索限制 (使用位置和宣告位置) 時。舉例來說,如果宣告網站版面配置限制 (C
) 為 struct Example { ... }:C;
之類,我們會在 :
分隔符和 ;
終結符之間明確標示限制。
統一列舉和聯集
從類型理論的角度來看,列舉代表一組單位類型,而聯集則代表任何類型的總和。因此,我們很想將這兩個概念整合為一個概念。這是支援 ADT 的程式設計語言 (例如 ML 或 Rust) 採用的方法。
不過,從版面配置的角度來看,單一單位類型的加總類型 (列舉) 比可擴充的對應項目 (聯集) 更有效率。雖然兩者都提供可擴充性,可在新增新成員時使用,但只有集合可從單位類型 (例如 struct
{}
) 擴充至任何類型。這種可擴充性會導致內嵌信封的成本。
我們選擇了務實的方法,以平衡兩個結構的複雜度,並利用特殊大小寫列舉的效能優勢。
參考資料
關於語法
關於可擴充方法引數
關於類型別名和命名類型
Footnote2
雖然選擇包裝類型比選擇明確的語法更能讓語法簡潔,但設定合理的預設值,可讓 FIDL 程式庫中的列舉更具一致性。這個術語提供遷移路徑,可在日後切換列舉,例如程式庫應定義一般用途的 ErrorStatus
列舉,日後可由另一個「更佳」的一般用途 ErrorStatusV2
取代。
-
或者,至少要先充分瞭解線路格式和相關注意事項,例如 https://fxrev.dev/360015 ↩