RFC-0050:FIDL 語法翻新

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

我們制定了語法選擇的指導原則,並根據這些原則進行一些語法變更。

作者
提交日期 (年月分)2020-02-26
審查日期 (年-月-日)2019-06-04

摘要

我們制定了語法選擇的指導原則,並根據這些原則進行一些語法變更。

變更

  • 將類型放在第二個位置 (例如,在方法參數名稱中,必須列在各自的類型之前),資料表宣告的成員名稱會排在其各自的類型之前。
  • 將類型變更為從限制分開,以便將版面配置相關類型資訊放在 : 分隔符的左側,其中包含限制資訊。舉例來說,array<T, 5>vector<T>:5 更能清楚表示陣列的大小會影響版面配置,但這是向量的限制。
  • 匿名版面配置」簡介。舉例來說,table { f1 uint8; f2 uint16; } 可以直接在方法參數清單內使用。
  • 如要宣告頂層類型,請使用匿名版面配置,並搭配採用 type Name = Layout; 格式的類型簡介宣告
  • 最後,針對通訊協定 P,將 Prequest<P> 分別renamingclient_end:Pserver_end:P。請注意,通訊協定是用戶端或伺服器端的限制,而非前一個位置,可能會錯誤指出版面配置的相關疑慮。

與其他 RFC 的關係

這個 RFC 後日修訂者為:

提振精神

入門範例

代數資料類型

語法用途廣泛,表示代數資料類型 (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;
    };
};

以模式來說,我們選擇使用 structunionunion 提供擴充能力,因此不需要 (最好也) 使用更彈性的變化版本。如果需要變更變化版本,我們可以改為新增一個批發商,然後改用這個新的變化版本。(在其他需要改進性的地方,例如二元或一元運算子清單,系統會選擇彈性列舉)。

支援 ADT 需要不只人體工學語法才能描述資料類型。例如,預期的其中一項主要功能是易於建構和銷毀 (例如透過模式比對或訪客模式)。

這個 RFC 不會為 FIDL 導入新功能,且對遞迴類型的限制將導致此範例無法在今天進行編譯。我們計劃新增一般化遞迴類型支援,且此擴充資料將成為未來 RFC 的物件。

更輕鬆地將非可變訊息與可不斷變化的訊息結合

舉例來說,表示一個「可延伸結構」,其中包含精簡、內嵌、快速編碼/解碼等 struct 元素,且可能具有擴充能力:

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 個欄位的可能性就有 2 個,可能會增加收件者的許多複雜性。

指導原則

FIDL 主要在定義應用程式二進位檔介面 (ABI) 疑慮,以及應用程式設計介面 (API) 相關問題。這可能會導致內容過於詳細,也可能比其他程式設計語言更詳細。舉例來說,聯集的 unit 變體會以空白結構表示,如上方的 InvocationPayload 範例所示。我們可以選擇引進語法糖來消除這種類型,但這可避免在中央位置反映 ABI 問題。

區隔版面配置與限制

依語法對齊

    layout:constraint

以類型來說,也就是任何控製版面配置的項目在冒號之前,控管限制的項目皆位於冒號後方。版面配置描述位元組如何配置與解譯方式。這項限制會限制特定版面配置代表的內容,這是在編碼/解碼期間完成的驗證步驟。

這個語法能以更簡單的方式將 ABI 變更納入考量,特別是可產生兩個簡短規則:

  1. 如果兩種類型有不同的版面配置,就無法在這兩種類型之間柔和轉換,反之亦然1,即變更左側會破壞 ABI
  2. 限制可以不斷演變,只要寫入者不再受限於讀者,就不會有相容性。例如,也許可以改變正確的方向並保留 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

如要瞭解這些異動,請參閱這個 RFC 的設計一節。

二進位線格式優先

雖然有許多格式可以代表 FIDL 訊息,但 FIDL 傳輸格式 (或「FIDL 二進位線路格式」) 是比較優先處理的一種,且依優先次而為。

這表示用來讓語法選項與 ABI 一致性保持一致的語法選擇應考慮使用二進位線路格式 (而非 JSON 等其他格式) 的 ABI。

例如,名稱在類型 ABI 時並不重要,通訊協定和方法的名稱「確實」很重要。雖然名稱可能對於可能的 JSON 格式而言很重要,但在選擇語法時,我們會選擇過度旋轉為二進位 ABI 格式,但如果會阻礙 ABI 規則的理解,就不會改變語法來利用文字表示方式。

最少功能

Wright 的「form and function should be one」(形式與函式應該應該一) 可讓我們設法找出相似的結構類似的含義,反之亦然。舉例來說,所有在內部利用信封的可延展資料,一律會以 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>:5vector<uint32>:<5> 皆可,且相等。

內建

系統支援下列原始類型

  • 布林值 bool
  • 帶號整數 int8int16int32int64
  • 非帶號整數 uint8uint16uint32uint64
  • IEEE 754 浮點 float32float64

固定大小的重複值

array<T, N>

這可以視為包含 T 類型的 N 元素的 struct

變數大小的重複值

vector<T>
vector<T>:N

也就是可省略大小 N

可變大小的 UTF-8 字串

string
string:N

也就是可省略大小 N

核心物件的參照,例如控點

handle
handle:S

子類型 Sbtibufferchanneldebuglogeventeventpairexceptionfifoguestinterruptiommujobpagerpcidevicepmtportprocessprofileresourceresourceresourcedebuglogeventeventpairexceptionfifoguestinterruptsocketsuspendtokenthreadtimervcpuvmarvmo

使用 RFC-0028:處理權利中引入的權利:

handle:<S, R>

其中 R 是權利值或權利運算式。

通訊協定物件參照,例如指定用途的管道控制代碼

client_end:P
server_end:P

例如 client_end:fuchsia.media.AudioCoreserver_end:fuchsia.ui.scenic.Session

具體來說,通訊協定本身並不合法:通訊協定宣告不會引入類型,只能視為用戶端或伺服器結束。進一步瞭解傳輸一般化一節的內容。

版面配置

除了內建版面配置之外,我們也有五種版面配置,可設定以導入新的類型:

  • enum
  • bits
  • struct
  • table
  • union

精簡版面配置

enumbits 版面配置的表示方式類似:

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;
    ...
};

可彈性的版面配置

tableunion 版面配置的表示方式類似:

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 中,偏好設定都是將類型節節中的屬性降低至版面配置,而不是將版面配置上的屬性提升至類型段落。

為版面配置命名及使用版面配置

Layout 本身不會沿用名稱,而所有版面配置皆是「匿名」。相反地,這是某個版面配置的特定用途,以決定其在譯文語言中的名稱。

舉例來說,版面配置的最常見用途是導入新的頂層類型:

library fuchsia.mem;

type Buffer = struct {
    vmo handle:vmo;
    size uint64;
};

在本例中,結構版面配置用於頂層程式庫中的「新類型」宣告。

簡介筆記會介紹如何以匿名方式使用的範例引數:

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/Bufferfuchsia.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 = ...,該宣告 type MethodRequestOfProtocool = ... 會與系統產生的簡化名稱衝突,該宣告會失敗。

透過繫結為結構定義命名

繫結可分成約兩種類別:

  1. 能夠以譯文語言表示對結構定義範圍的命名,例如 C++ 語言的繫結;
  2. 無法呈現命名結構定義,以及使用產生的經過簡化的扁平化名稱,例如 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 中最初提議的語法不同。

選填

某些類型原本能夠選用:vectorsstringsenvelopes 和使用這類結構的版面配置。例如,table 是信封向量,union 是標記加信封。因此,這些類型無論為選用或不是限制,可進化為 (可為空值、透過放寬限制,也可發展成空值) 或發展後 (必須這麼做,透過放寬限制來完成)。

另一方面,int8struct 版面配置等類型本身就不能選用。為了具有選擇性性,您必須引入間接指令,例如透過結構體中的間接參照來實現。因此,與本身只能選用的類型不同,轉換路徑可能也不會改變。

為了區分這兩種情況,並遵循將 ABI 問題「在左側」和可變動的考量原則「在右側」:

自然為選填 非自然選填
string:optional box<struct>
vector:optional
union:optional

妥善命名,建議使用「選用」、「必要」、「出現」和「缺失」這些字詞。(We should avoid "nullable", "not nullable", "null fields".) 為了配合該命名偏好,我們選擇的是 box<T>,而不是 pointer<T>box 是預設結構的選用屬性,也就是新語法中的 box<struct> 相當於舊語法的 struct?box<struct>:optional 是多餘的,可能會觸發編譯器或 Linter 的警告。這可以更符合我們預期的用途:使用者一般的方塊結構體會自己具有選擇性性,而不是新增間接結構。

常數

常數宣告為:

const NAME type = expression;

限制排序

根據版面配置和限制將類型參數化時,系統會針對特定類型,固定這些引數的排序方式。此 RFC 定義了以下限制條件的順序 (目前沒有任何類型有多個版面配置引數):

  • 帳號代碼:子類型、權限、選用項目。
  • 通訊協定 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
};

信封限制

表格和可延伸聯集的語法會隱藏信封使用:

  • tablevector<envelope<...>>,而
  • union 是一種 struct { tag uint64; variant envelope<...>; }

目前,只有信封存在的地方只有 tableunion 宣告中的 ordinal:,因此將這個語法視為信封的「糖」引進會特別有幫助。簡單來說,我們可以 去糖化,方法如下:

說明資料表和彈性聯集
table ExampleTable {
    1: name string;
    2: size uint32;
};
table ExampleTable {
    @1 name envelope;
    @2 size envelope;
};
union ExampleUnion {
    1: name string;
    2: size uint32;
};
union ExampleUnion {
    @1 name envelope;
    @2 size envelope;
};

如要限制 envelope (例如對元素 require),我們會將這項限制放在序對 ordinal:C 上,例如:

說明資料表和彈性聯集
table ExampleTable {
    1:C1 name string:C2;
    2:C size uint32;
};
table ExampleTable {
    @1 name envelope:C1;
    @2 size envelope:C;
};
union ExampleUnion {
    1:C1 name string:C2;
    2:C size uint32;
};
union ExampleUnion {
    @1 name envelope:C1;
    @2 size envelope:C;
};

屬性

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;
    };
};

傳輸一般化

宣告新的傳輸至少需要定義新名稱、指定傳輸支援的訊息限制 (例如「無控制」、「無資料表」),以及指定通訊協定的限制 (例如僅限「fire-and-forget 方法」、「無事件」。

隱蔽語法類似於以無型 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 的其他語言的對等項目),而且其種類只有核心公開的物件,例如 portvmofifo 等。

在考慮其他情況 (例如處理序通訊) 時,最理想的擴充點是可以直接在 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 = ...;
};

這會允許 handlehandle:VMO (或其他程式庫 zx.handle:zx.handle.VMO)。

存在實驗性實作項目,將用來破壞 Zircon 和 FIDL 之間的循環依附元件 (在這項變更之前,FIDL 曾描述 Zircon 的 API,但 FIDL 根據 Zircon 的 API 而有一部分定義)。

執行策略

系統會在所有 .fidl 檔案的頂端新增臨時的「版本宣告」,供 fidlc 用於偵測 .fidl 檔案是否位於先前或新語法中。

此權杖緊接在程式庫陳述式之前:

// Copyright notice...

deprecated_syntax;

library fidl.test;
...

建議您使用明確標記,以便簡化 fidlc 的角色偵測語法,並提高可讀性。舉例來說,將語法解讀為任一語法會導致編譯錯誤,即為偵測語法的一大挑戰。這類情境需要經驗法則,才能判斷新舊語法之間的差異,導致出現出乎意料的結果。

此外,這個權杖會以舊語法而非新語法 (例如 new_syntax;") 新增至所有檔案,以便提供近期遷移作業的相關資訊。FIDL 檔案的讀取者可以理解語法即將變更,且可透過其他管道 (例如說明文件、郵寄清單) 尋找其他資訊。

我們將新增 fidlconv 主機工具,該工具可使用舊格式的 FIDL 檔案,並將其轉換為採用新格式的檔案 (稱為 .fidl_new),進行本節作業。雖然這項工具與 fidlc 不同,但需要使用編譯器的內部表示法,才能正確執行這項轉換作業。舉例來說,只有在 Foo 是通訊協定的情況下,才需要將類型 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 團隊也會將程式設計資料表後端移至獨立二進位檔 (與其他後端一樣),並淘汰並刪除 C 繫結後端,方法是產生上次使用的時間,然後在 fuchsia.git 樹狀結構存放區中檢查這些繫結。

人體工學

這個 RFC 的指南與人體工學相關。

對於熟悉目前語法的開發人員,我們會願意犧牲短期生產力,因為他們會重新訓練使用這個經修改的語法,因為我們深信未來將有更多開發人員將受益於 FIDL。

說明文件與範例

這需要改變:

回溯相容性

這項變更無法回溯相容。請參閱轉換計畫的實作一節。

效能

這項變更不會影響成效。

安全性

這項變更對安全性沒有影響。

測試

請參閱轉換計畫的實作一節,確認其正確性。

缺點、替代與不明

使用冒號分隔名稱與類型

由於我們要將類型轉向第二個,因此也可以考慮使用相當常見的 : 分隔符,方法與類型理論相同:Rust、Kotlin、機器學習語言 (SML、Hakell、OCaml)、Scala、Nim、Python、TypeScript 等:

    field: int32 rather than the proposed field int32

這個提案會拒絕這種方法。

: 分隔符的用途是區分版面配置與限制。這也用於表示 enumbits 宣告的「包裝類型」。最後則用於在 tableunion 宣告中表示信封。進一步超載 : 分隔符,特別是與主要使用的文法緊鄰時,就會造成混淆 (例如資料表成員 1: name: string:128;)。

省略分號

我們已經討論如何省略分號終止宣告 (可以是成員、常數或其他)。

本提案選擇不探討這個簡化過程。

移除分號會造成 FIDL 作者的語法差異不大。這也不是一項重要的變更,未來應該都要能輕鬆修改 (例如 Go 的移除分號的方法)。

但是,一旦出現分號來終止成員,而宣告會更加輕鬆地保證模稜兩可的文法規則,特別是在探索限制 (使用網站和宣告網站) 時。舉例來說,透過 struct Example { ... }:C; 這類宣告網站版面配置限制 (C),我們會在 : 分隔符和 ; 結束字元之間建立適當的限制。

統合列舉和聯集

從類型理論的角度來看,列舉代表單位類型的總和,而聯集代表任何類型的加總。因此,您可能會想將這兩個概念整合為一個概念。這是透過支援 ADT 的程式設計語言 (例如 ML 或 Rust) 採取的做法。

不過,從版面配置的角度來看,僅包含單元類型的總和 (列舉) 能以比可延伸對應 (聯集) 更有效率的表示。雖然兩者都能在新增成員時更有彈性,但只有聯集能夠擴充從單元類型 (例如 struct {}) 到任何類型。不過,這種擴充性是內嵌信封的代價;

我們選擇了務實的方法,兼顧有兩個結構的複雜性,同時具有特殊大小寫列舉的效能優勢。

參考資料

使用語法

在可延伸方法引數上

類型別名和已命名類型

Footnote2

相較於明確選擇已包裝的類型,採用語法精簡可能比較困難,但有合理的預設值,可為跨 FIDL 程式庫中的列舉提供更高的一致性。這個概念提供了讓列舉未來切換的遷移路徑。舉例來說,如果程式庫定義了一般用途的 ErrorStatus 列舉,之後可替換為另一個「更佳」的一般用途 ErrorStatusV2


  1. 或者,至少不太瞭解線路格式和注意事項,例如:https://fxrev.dev/360015