FIDL 編譯器錯誤目錄

本文件列出 FIDL 編譯器 fidlc 發出的所有錯誤。這個網域中的錯誤 ID 一律會以 fi- 做為前置字串,後面加上四位數代碼,例如 fi-0123。

fi-0001:無效字元

詞法分析器無法在指定位置將字元轉換為權杖。

library test.bad.fi0001;

type ßar = struct {
    value uint64;
};

請替換或移除無效字元。

library test.good.fi0001;

type Foo = struct {
    value uint64;
};

無效字元因位置而異。請參閱 FIDL 語言規格,判斷 FIDL 語法各部分允許哪些字元。

fi-0002:非預期的換行

字串常值不得拆分為多行:

library test.bad.fi0002;

const BAD_STRING string:1 = "Hello
World";

請改用逸出序列 \n 表示換行:

library test.good.fi0002;

const GOOD_STRING string:11 = "Hello\nWorld";

fi-0003:逸出序列無效

詞法分析器在逸出序列開頭遇到無效字元。

library test.bad.fi0003;

const UNESCAPED_BACKSLASH string:2 = "\ ";
const BACKSLASH_TYPO string:1 = "\i";
const CODE_POINT_TYPO string:1 = "\Y1F604";

請改用有效字元來開始逸出序列,或移除不小心加入的反斜線字元。

library test.good.fi0003;

const ESCAPED_BACKSLASH string:2 = "\\ ";
const REMOVED_BACKSLASH string:1 = "i";
const SMALL_CODE_POINT string:3 = "\u{2604}";
const BIG_CODE_POINT string:4 = "\u{01F604}";

如需有效的逸出序列,請參閱 FIDL 文法規格。

fi-0004:十六進位數字無效

字串常值中的 Unicode 逸出字元不得包含無效的十六進位數字:

library test.bad.fi0004;

const SMILE string = "\u{1G600}";

您必須以十六進位格式指定有效的 Unicode 碼點,範圍為 0 至 10FFFF。每個十六進位數字必須是 0 到 9 的數字、a 到 f 的小寫字母,或 A 到 F 的大寫字母。在本例中,G 是 F 的錯字:

library test.good.fi0004;

const SMILE string = "\u{1F600}";

fi-0005

fi-0006:預期宣告

如果 FIDL 預期會出現宣告,但找到其他內容,就會發生這個錯誤。這通常是拼寫錯誤所致。有效宣告包括:type、alias、const、using、protocol 和 service。

library test.bad.fi0006;

cosnt SPELLED_CONST_WRONG string:2 = ":("; // Expected a declaraction (such as const).

如要修正這項錯誤,請檢查頂層宣告是否有錯字,並確認你只使用 FIDL 支援的功能。

library test.good.fi0006;

const SPELLED_CONST_RIGHT string:2 = ":)";

fi-0007:權杖不如預期

剖析期間遇到非預期的權杖時,就會發生這個錯誤。一般來說,這是因為輸入錯誤:

library test.bad.fi0007;

alias MyType = vector<uint8>:<,256,optional>; // Extra leading comma

這個問題的修正方式通常是移除未預期的符號,或在某些情況下提供缺少的語法:

library test.good.fi0007;

alias MyType = vector<uint8>:<256, optional>;

fi-0008:權杖不如預期

每當 FIDL 剖析器遇到語法無效的符記,就會發生這個錯誤。這可能有多種原因,例如列舉成員缺少 =、多出 library = what.is.that.equals.doing.there 等符號。

library test.bad.unexpectedtokenofkind;

type Numbers = flexible enum {
    ONE; // FIDL enums don't have a default value.
};

一般來說,只要新增缺少的權杖或移除多餘的權杖,即可修正這個問題。

library test.good.fi0008;

type Numbers = flexible enum {
    ONE = 1;
};

為避免發生這項錯誤,請檢查 *.fidl 檔案,確保語法正確。

fi-0009:ID 不符預期

這個錯誤通常是因為 ID 使用位置不正確:

using test.bad.fi0009;

請改用正確的 ID:

library test.good.fi0009;

fi-0010:ID 無效

系統發現 ID 不符合有效 ID 的規定。FIDL 識別碼可包含英數字元和底線 (具體來說是 A-Z、a-z、0-9 和 _),此外,每個識別碼都必須以字母開頭,並以字母或數字結尾。

library test.bad.fi0010a;

// Foo_ is not a valid identifier because it ends with '_'.
type Foo_ = struct {
    value uint64;
};

如要修正這個問題,請變更 ID,確保 ID 只包含有效字元、開頭為英文字母,且結尾為英文字母或數字。

library test.good.fi0010a;

type Foo = struct {
    value uint64;
};

如果將多部分 (點分隔) 識別碼傳遞至屬性,也可能發生這個錯誤。

library test.bad.fi0010b;

@foo(bar.baz="Bar", zork="Zoom")
type Empty = struct{};

如要修正這個問題,請改為只在屬性中使用單一部分 ID。

library test.good.fi0010b;

@foo(bar="Bar", zork="Zoom")
type Empty = struct {};

fi-0011:程式庫名稱元件無效

程式庫名稱只能包含字母和數字 (A-Z、a-z 和 0-9),且開頭必須是字母。

library test.bad.fi0011.name_with_underscores;

如要修正這個問題,請確認所有程式庫名稱元件都符合規定。

library test.good.fi0011.namewithoutunderscores;

fi-0012:類型版面配置類別無效

類型宣告必須指定 FIDL 已知的版面配置:

library test.bad.fi00012;

type Foo = invalid {};

有效的版面配置為 bits、enum、struct、table 和 union:

library test.good.fi0012;

type Empty = struct {};

「版面配置」是 FIDL 類型的參數化說明。這代表一組可能類型,可接收更多引數來指定其形狀。舉例來說,struct 是一種版面配置,在定義特定成員時會變成具體類型,而 array 則是一種版面配置,在指定要依序重複指定次數的類型時會變成具體類型。

版面配置都內建於 FIDL 語言中,使用者無法指定自己的版面配置,也無法建立自己的泛型範本。

fi-0013: Invalid wrapped type

如果傳遞至列舉或位元宣告的值不是類型的 ID,就會發生這項錯誤,例如您改為提供字串值做為「支援類型」:

library test.bad.fi0013;

type TypeDecl = enum : "int32" {
    FOO = 1;
    BAR = 2;
};

如要修正這項錯誤,請確認列舉或位元的支援類型是類型 ID。

library test.good.fi0013;

type TypeDecl = enum : int32 {
    FOO = 1;
    BAR = 2;
};

fi-0014:屬性含有空白括號

如果屬性有括號但沒有引數,就會發生這項錯誤。

library test.bad.fi0014;

@discoverable()
protocol MyProtocol {};

如要修正這個問題,請移除不含引數的屬性括號,或在需要引數時提供引數。

library test.good.fi0014;

@discoverable
protocol MyProtocol {};

FIDL 不允許屬性上有空白引數清單,主要是為了維持風格。

fi-0015:屬性引數必須全部命名

為求明確,當屬性有多個引數時,必須明確命名所有屬性的引數。

如果屬性有多個引數,但未明確提供引數名稱,就會發生這項錯誤。

library test.bad.fi0015;

@foo("abc", "def")
type MyStruct = struct {};

如要修正這個問題,請使用 name=value 語法為所有引數提供名稱。

library test.good.fi0015;

@foo(bar="abc", baz="def")
type MyStruct = struct {};

fi-0016:成員前缺少序數

如果聯集或資料表中的欄位缺少序數,就會發生這項錯誤。

library test.bad.fi0016a;

type Foo = table {
    x int64;
};
library test.bad.fi0016b;

type Bar = union {
    foo int64;
    bar vector<uint32>:10;
};

如要修正這項錯誤,請明確指定資料表或聯集的序數:

library test.good.fi0016;

type Foo = table {
    1: x int64;
};

type Bar = union {
    1: foo int64;
    2: bar vector<uint32>:10;
};

與 struct 不同,資料表和聯集可對內容進行回溯相容變更。為啟用這項功能,必須使用一致的值 (序數) 來識別資料表欄位或聯集變體。為避免混淆,並降低在變更資料表或聯集時意外變更序數的可能性,請務必明確指定序數。

fi-0017:序數超出範圍

資料表和 UNION 的序數必須是有效的無正負號 32 位元整數。如果序數為負數或大於 4,294,967,295,就會發生這個錯誤。

library test.bad.fi0017a;

type Foo = table {
  -1: foo string;
};
library test.bad.fi0017b;

type Bar = union {
  -1: foo string;
};

如要修正這項錯誤,請確認所有序數都在允許範圍內。

library test.good.fi0017;

type Foo = table {
    1: foo string;
};

type Bar = union {
    1: foo string;
};

fi-0018:序數必須從 1 開始

table 和 union 成員序數值都不得為 0:

library test.bad.fi0018;

type Foo = strict union {
    0: foo uint32;
    1: bar uint64;
};

編號應從 1 開始:

library test.good.fi0018;

type Foo = strict union {
    1: foo uint32;
    2: bar uint64;
};

fi-0019:嚴格位元、列舉或聯集不得空白

嚴格的 bits、enum 或 union 不得有零個成員:

library test.bad.fi0019;

type Numbers = strict enum {};

而是必須至少有一名成員:

library test.good.fi0019a;

type Numbers = flexible enum {};

或者,您也可以標記宣告 flexible,而非 strict:

library test.good.fi0019b;

type Numbers = strict enum {
    ONE = 1;
};

空白的 bits、enum 或 union 不含任何資訊,因此通常不應在 API 中使用。不過,彈性資料類型是為了演進而設計,因此定義一開始為空的彈性位元或列舉,並預期日後新增成員,是合理的做法。定義新的資料類型時,請務必仔細考慮要使用 strict 還是 flexible。

fi-0020:通訊協定成員無效

如果通訊協定中的項目未辨識為有效的通訊協定成員,例如通訊協定中的項目不是通訊協定組合、單向方法、雙向方法或事件,就會發生這項錯誤。

library test.bad.fi0020;

protocol Example {
    NotAMethodOrCompose;
};

如要修正這項錯誤,請移除無效項目,或將其轉換為正確的語法,以符合預期的通訊協定項目類型。

library test.good.fi0020;

protocol Example {
    AMethod();
};

fi-0021

fi-0022:無法將屬性附加至 ID

如果屬性位於宣告類型上,且該類型為 ID 類型,就會發生這項錯誤。舉例來說,在 struct 宣告中,將屬性放在欄位名稱之後、欄位類型之前,會將屬性與欄位類型建立關聯,而非與欄位本身建立關聯。如果欄位類型是依名稱參照的現有類型,則無法套用其他屬性。

library test.bad.fi0022;

type Foo = struct {
    // uint32 is an existing type, extra attributes cannot be added to it just
    // for this field.
    data @foo uint32;
};

如果意圖是將屬性套用至欄位,則應將屬性移至欄位名稱之前。

屬性可套用至宣告屬性的類型。也就是說,如果 struct 欄位或其他類似宣告的類型是匿名類型,而非 ID 類型,則可將屬性套用至該類型。

library test.good.fi0022;

type Foo = struct {
    // The foo attribute is associated with the data1 field, not the uint32
    // type.
    @foo
    data1 uint32;
    // The type of data2 is a newly declared anonymous structure, so that new
    // type can have an attribute applied to it.
    data2 @foo struct {};
};

fi-0023:類型宣告中的屬性

使用內嵌版面配置,可以直接在版面配置前放置屬性。不過,在頂層宣告類型時,您無法:

library test.bad.fi0023;

type Foo = @foo struct {};

您必須將屬性放在 type 關鍵字前面:

library test.good.fi0023;

@foo
type Foo = struct {};

我們強制執行這項規定,是因為允許在兩個地方使用屬性會造成混淆。

fi-0024:方法參數清單的文件註解

方法參數清單不得包含文件註解:

library test.bad.fi0024;

protocol Example {
    Method(/// This is a one-way method.
            struct {
        b bool;
    });
};

目前請將文件註解放在方法本身:

library test.good.fi0024;

protocol Example {
    /// This is a one-way method.
    Method(struct {
        b bool;
    });
};

這項錯誤解決後,就不會再發生這個問題。這個錯誤是遷移作業的遺留問題,用於說明使用 FIDL 類型 (而非參數清單) 的方法酬載。

fi-0025:匯入群組必須位於檔案頂端

除了檔案頂端的 library 宣告外,檔案的 using 匯入項目 (如有) 前方不得有任何其他宣告:

library test.bad.fi0025;

alias i16 = int16;
using dependent;

type UsesDependent = struct {
    field dependent.Something;
};

如要解決這項錯誤,請將所有 using 匯入項目放在 library 宣告後方的區塊中:

library test.good.fi0025;

using dependent;

alias i16 = int16;
type UsesDependent = struct {
    field dependent.Something;
};

這項規則反映 FIDL 團隊主要考量美觀,認為依附元件妥善分組且容易找到時,會更容易閱讀。

fi-0026:文件註解區塊內的註解

註解不應放在文件註解區塊內:

library test.bad.fi0026;

/// start
// middle
/// end
type Empty = struct {};

註解應放在文件註解區塊之前或之後:

library test.good.fi0026;

// some comments above,
// maybe about the doc comment
/// A
/// multiline
/// comment!
// another comment, maybe about the struct
type Empty = struct {};

一般來說,文件註解區塊之前的註解,最適合用來註解文件註解本身。

fi-0027:文件註解區塊中有空白行

文件註解區塊中不應有空白行:

library test.bad.fi0027;

/// start

/// end
type Empty = struct {};

空白行應放在文件註解區塊的前後:

library test.good.fi0027a;

/// A doc comment
type Empty = struct {};

或者,您也可以完全省略空白行:

library test.good.fi0027b;

/// A doc comment
type Empty = struct {};

fi-0028:文件註解後方必須加上宣告

文件註解一律不得像一般註解一樣自由浮動:

library test.bad.fi0028;

type Empty = struct {};
/// bad

在任何情況下,文件註解都必須直接位於 FIDL 宣告之前:

library test.good.fi0028a;

/// A doc comment
type Empty = struct {};

FIDL 會在編譯期間將文件註解「降低」為 @doc 屬性。事實上,如有需要,任何註解都可以直接以這種方式編寫:

library test.good.fi0028b;

@doc("An attribute doc comment")
type Empty = struct {};

從技術角度來看,獨立文件註解無法編譯,但語意上也令人困惑:什麼是「文件」?與一般註解不同,文件註解會處理成結構化文件,因此必須清楚指出附加的 FIDL 結構。

fi-0029:資源定義必須至少有一項屬性

禁止使用未指定屬性的資源定義:

library test.bad.resourcedefinitionnoproperties;

resource_definition SomeResource : uint32 {
  properties {};
};

請至少指定一項屬性:

library test.good.fi0029;

resource_definition SomeResource : uint32 {
    properties {
        subtype strict enum : uint32 {
            NONE = 0;
        };
    };
};

這項錯誤與 FIDL 的內部實作有關,因此只會向開發 FIDL 核心程式庫的開發人員顯示。使用者不應看到這項錯誤。

它參照的 resource_definition 宣告是 FIDL 定義控點等資源的內部方法,日後可能會隨著控點一般化作業而變更。

fi-0030:修飾符無效

每個 FIDL 修飾符都有一組可使用的特定宣告。不得在禁止的宣告中使用修飾符:

library test.bad.fi0030;

type MyStruct = strict struct {
    i int32;
};

最佳做法是移除有問題的修飾符:

library test.good.fi0030;

type MyStruct = struct {
    i int32;
};

fi-0031:只有位元和列舉可以有子類型

並非所有 FIDL 版面配置都能包含子類型:

library test.bad.fi0031;

type Foo = flexible union : uint32 {};

只有 bits 和 enum 版面配置是根據基礎類型定義。

library test.good.fi0031;

type Foo = flexible enum : uint32 {};

bits 和 enum 版面配置有些獨特,因為它們只是整數 FIDL 原始類型的受限子類型。因此,他們可以指定做為這個子類型的基礎類型。相反地,struct、table 和 union 版面配置可以任意放大,並包含許多成員,因此全域、版面配置範圍的子類型不合理。

fi-0032:不允許重複的修飾符

禁止在單一宣告中指定相同的修飾符:

library test.bad.fi0032;

type MyUnion = strict resource strict union {
    1: foo bool;
};

移除重複的修飾符:

library test.good.fi0032;

type MyUnion = resource strict union {
    1: foo bool;
};

fi-0033:修飾符衝突

某些修飾符彼此互斥,無法同時修飾同一個宣告:

library test.bad.conflictingmodifiers;

type StrictFlexibleFoo = strict flexible union {
    1: b bool;
};

type FlexibleStrictBar = flexible strict union {
    1: b bool;
};

一次只能在單一宣告中使用 strict 或 flexible 修飾符其中之一:

library test.good.fi0033;

type FlexibleFoo = flexible union {
    1: i int32;
};

type StrictBar = strict union {
    1: i int32;
};

目前只有 strict 和 flexible 修飾符會以這種方式互斥。resource 修飾符沒有對應的修飾符,因此不受這類限制。

fi-0034:名稱衝突

兩個宣告不得使用相同名稱:

library test.bad.fi0034;

const COLOR string = "red";
const COLOR string = "blue";

請改為每個宣告指定不重複的名稱:

library test.good.fi0034b;

const COLOR string = "red";
const OTHER_COLOR string = "blue";

或者,如果誤加其中一個宣告,請將其移除:

library test.good.fi0034a;

const COLOR string = "red";

如要進一步瞭解如何選擇名稱,請參閱 FIDL 樣式指南。

fi-0035:正規名稱衝突

兩個宣告不得使用相同的標準名稱:

library test.bad.fi0035;

const COLOR string = "red";

protocol Color {};

雖然 COLOR 和 Color 看起來不同,但兩者都以標準名稱 color 表示。將原始名稱轉換為 snake_case,即可取得正規名稱。

如要修正錯誤,請為每個宣告指定標準化後不重複的名稱:

library test.good.fi0035;

const COLOR string = "red";

protocol ColorMixer {};

遵循 FIDL 樣式指南的命名規範,可盡量避免發生這項錯誤。使用相同大小寫樣式的宣告之間絕不會發生標準名稱衝突,而使用不同樣式的宣告之間也很少發生衝突,因為有其他要求 (例如通訊協定名稱通常應為以 -er 結尾的名詞片語)。

FIDL 會強制執行這項規則,因為繫結產生器會將名稱轉換為譯文語言的慣用命名樣式。確保標準名稱不重複,即可保證繫結能執行這項操作,而不會產生名稱衝突。詳情請參閱 RFC-0040:ID 專屬性。

fi-0036:名稱重疊

同名宣告的可用性不得重疊:

@available(added=1)
library test.bad.fi0036;

type Color = strict enum {
    RED = 1;
};

@available(added=2)
type Color = flexible enum {
    RED = 1;
};

請改用 @available 屬性,確保任何版本都只有一個宣告:

@available(added=1)
library test.good.fi0036;

@available(replaced=2)
type Color = strict enum {
    RED = 1;
};

@available(added=2)
type Color = flexible enum {
    RED = 1;
};

或者,您也可以重新命名或移除其中一個宣告,如 fi-0034 所示。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0037:正規名稱重疊

具有相同標準名稱的宣告,其可用性不得重疊:

@available(added=1)
library test.bad.fi0037;

const COLOR string = "red";

@available(added=2)
protocol Color {};

雖然 COLOR 和 Color 看起來不同,但兩者都以標準名稱 color 表示。將原始名稱轉換為 snake_case,即可取得正規名稱。

如要修正錯誤,請為每個宣告指定正規化後不重複的名稱。

@available(added=1)
library test.good.fi0037;

const COLOR string = "red";

@available(added=2)
protocol ColorMixer {};

或者,您也可以按照 fi-0036 所示,變更其中一個宣告的可用性,或移除宣告。

如要進一步瞭解 FIDL 為何要求宣告必須具有不重複的標準名稱,請參閱 fi-0035。

fi-0038:名稱與匯入項目衝突

宣告名稱不得與使用 using 匯入的程式庫相同:

library dependency;

const VALUE uint32 = 1;
library test.bad.fi0038b;

using dependency;

type dependency = struct {};

// Without this, we'd get fi-0178 instead.
const USE_VALUE uint32 = dependency.VALUE;

請改用 using ... as 語法,以其他名稱匯入程式庫:

library test.good.fi0038b;

using dependency as dep;

type dependency = struct {};

const USE_VALUE uint32 = dep.VALUE;

或者,您也可以重新命名宣告,避免衝突:

library test.good.fi0038c;

using dependency;

type OtherName = struct {};

const USE_VALUE uint32 = dependency.VALUE;

只要在程式庫名稱中使用多個元件,即可避免這個問題。舉例來說,Fuchsia SDK 中的 FIDL 程式庫開頭為 fuchsia.,因此至少有兩個元件,且不得與宣告名稱衝突。

這項錯誤是為了避免語意不清。舉例來說,如果 dependency 是具有名為 VALUE 成員的列舉,則 dependency.VALUE 是指該列舉成員,還是指在匯入程式庫中宣告的常數,就會變得模稜兩可。

fi-0039:正規名稱與匯入項目衝突

宣告的標準名稱不得與使用 using 的程式庫匯入項目相同:

library dependency;

const VALUE uint32 = 1;
library test.bad.fi0039b;

using dependency;

type Dependency = struct {};

// Without this, we'd get fi-0178 instead.
const USE_VALUE uint32 = dependency.VALUE;

雖然 dependency 和 Dependency 看起來不同,但兩者都以標準名稱 dependency 表示。將原始名稱轉換為 snake_case,即可取得正規名稱。

如要修正錯誤,請使用 using ... as 語法,以其他名稱匯入程式庫:

library test.good.fi0039b;

using dependency as dep;

type Dependency = struct {};

const USE_VALUE uint32 = dep.VALUE;

或者,您也可以重新命名宣告,避免衝突:

library test.good.fi0039c;

using dependency;

type OtherName = struct {};

const USE_VALUE uint32 = dependency.VALUE;

請參閱 fi-0038,瞭解這項錯誤的原因和避免方法。

如要進一步瞭解 FIDL 為何要求宣告必須具有不重複的標準名稱,請參閱 fi-0035。

fi-0040:檔案的程式庫名稱不一致

程式庫可以由多個檔案組成,但每個檔案都必須使用相同名稱:

library test.bad.fi0040a;
library test.bad.fi0040b;

請確認程式庫使用的所有檔案名稱皆相同:

library test.good.fi0040;
library test.good.fi0040;

對於多檔案程式庫,建議建立一個空白的 overview.fidl 檔案,做為程式庫的主要「進入點」。overview.fidl 檔案也適合用來放置程式庫範圍的 @available 平台規格。

fi-0041:多個程式庫名稱相同

傳遞至 fidlc 的每個程式庫都必須有不重複的名稱:

library test.bad.fi0041;
library test.bad.fi0041;

確認所有程式庫的名稱皆不重複:

library test.good.fi0041a;
library test.good.fi0041b;

這項錯誤通常是因為引數提供給 fidlc 的方式不正確。編譯所需的每個程式庫 (即編譯中的程式庫及其所有遞移依附元件) 的組成檔案,必須以單一空格分隔的檔案清單形式提供,並透過 --files 引數傳遞,每個程式庫一個這類旗標。常見的錯誤是嘗試在單一 --files 清單中傳遞所有程式庫的檔案。

fi-0042:重複匯入程式庫

依附元件不得匯入多次:

library test.bad.fi0042a;

type Bar = struct {};
library test.bad.fi0042b;

using test.bad.fi0042a;
using test.bad.fi0042a; // duplicated
type Foo = struct {
    bar test.bad.fi0042a.Bar;
};

請確保每個依附元件只匯入一次:

library test.good.fi0042a;

type Bar = struct {};
library test.good.fi0042b;

using test.good.fi0042a;
type Foo = struct {
    bar test.good.fi0042a.Bar;
};

值得注意的是,FIDL 不支援匯入相同程式庫的不同版本。系統會透過 --available 旗標,為整個 fidlc 編譯解析 @available 版本,這表示在任何指定的編譯執行作業中,編譯的程式庫及其所有依附元件都必須共用相同版本。

fi-0043:匯入的程式庫發生衝突

匯入的程式庫不得使用別名,以免與其他匯入程式庫的非別名名稱衝突:

library test.bad.fi0043a;

type Bar = struct {};

// This library has a one component name to demonstrate the error.
library fi0043b;

type Baz = struct {};
library test.bad.fi0043c;

using test.bad.fi0043a as fi0043b; // conflict
using fi0043b;

type Foo = struct {
    a fi0043b.Bar;
    b fi0043b.Baz;
};

請選擇其他別名,解決名稱衝突:

library test.good.fi0043a;

type Bar = struct {};
library fi0043b;

type Baz = struct {};
library test.good.fi0043c;

using test.good.fi0043a as dep;
using fi0043b;

type Foo = struct {
    a dep.Bar;
    b fi0043b.Baz;
};

fi-0044:程式庫匯入別名衝突

匯入的程式庫不得使用與其他匯入程式庫相同的別名:

library test.bad.fi0044a;

type Bar = struct {};
library test.bad.fi0044b;

type Baz = struct {};
library test.bad.fi0044c;

using test.bad.fi0044a as dep;
using test.bad.fi0044b as dep; // conflict
type Foo = struct {
    a dep.Bar;
    b dep.Baz;
};

選擇不衝突的別名,解決名稱衝突:

library test.good.fi0044a;

type Bar = struct {};
library test.good.fi0044b;

type Baz = struct {};
library test.good.fi0044c;

using test.good.fi0044a as dep1;
using test.good.fi0044b as dep2;
type Foo = struct {
    a dep1.Bar;
    b dep2.Baz;
};

fi-0045:使用宣告時不允許屬性

屬性無法附加至 using 宣告:

library test.bad.fi0045a;

type Bar = struct {};
library test.bad.fi0045b;

/// not allowed
@also_not_allowed
using test.bad.fi0045a;

type Foo = struct {
    bar test.bad.fi0045a.Bar;
};

移除屬性即可解決錯誤:

library test.good.fi0045a;

type Bar = struct {};
library test.good.fi0045b;

using test.good.fi0045a;

type Foo = struct {
    bar test.good.fi0045a.Bar;
};

這項限制也適用於 /// ... 文件註解,因為這些註解只是 @doc("...") 屬性的語法糖。

fi-0046:不明程式庫

在大多數情況下,這個問題是因為依附元件拼寫錯誤,或建構系統未提供依附元件。如果刻意不使用有問題的依附元件,則必須移除相關的 using 行:

library test.bad.fi0046;

using dependent; // unknown using.

type Foo = struct {
    dep dependent.Bar;
};

請務必使用建構系統,將所有匯入項目新增為程式庫的依附元件。

library test.good.fi0046;

type Foo = struct {
    dep int64;
};

fi-0047

fi-0048:選用表格成員

表格成員類型不得為 optional:

library test.bad.fi0048;

type Foo = table {
    // Strings can be optional in general, but not in table member position.
    1: t string:optional;
};

從所有成員中移除 optional 限制:

library test.good.fi0048;

type Foo = table {
    1: t string;
};

表格成員一律為選用,因此在成員的基礎類型上指定這項事實是多餘的。

資料表成員一律為選用,因為在傳輸時,每個資料表成員都會以向量中的項目表示。這個向量一律代表資料表中的所有已知欄位,因此每個省略的資料表成員都會以空值封包表示,這與省略的選用類型表示法完全相同。

fi-0049: Optional union member

聯集成員不得為選用:

library test.bad.fi0049;

type Foo = strict union {
    // Strings can be optional in general, but not in unions.
    1: bar string:optional;
};

移除 optional 限制:

library test.good.fi0049;

type Foo = strict union {
    1: bar string;
};

FIDL 不允許聯集成員為選用,因為這可能導致以多種方式表示相同的值。舉例來說,具有三個選用成員的聯集會有 6 個狀態 (每個成員 2 個)。而應使用類型為 struct {} 的第四個成員建立模型,或使用 Foo:optional 將整體聯集設為選用。

fi-0050:禁止使用已淘汰的 struct 預設語法

先前,FIDL 允許在 struct 成員上設定預設值:

library test.bad.fi0050;

type MyStruct = struct {
    field int64 = 20;
};

自 RFC-0160:移除對 FIDL struct 預設值的支援起,系統不允許這項行為:

library test.good.fi0050;

type MyStruct = struct {
    field int64;
};

struct 成員不得再使用預設值。使用者應改為在應用程式邏輯中設定這類預設值。

編譯器內建許可清單,允許少數使用此語法的舊版使用者繼續使用,但不會再新增例外狀況。這些使用者遷移後,這項功能就會從 FIDL 永久移除。

fi-0051:不明依附程式庫

如果使用的符號來自不明程式庫,就會發生這個錯誤。

library test.bad.fi0051;

type Company = table {
    1: employees vector<unknown.dependent.library.Person>;
    2: name string;
};

如要修正這個問題,請使用 using 宣告匯入缺少的依附程式庫。

library known.dependent.library;

type Person = table {
    1: age uint8;
    2: name string;
};
library test.good.fi0051;
using known.dependent.library;

type Company = table {
    1: employees vector<known.dependent.library.Person>;
    2: name string;
};

如果 fidlc 指令列叫用格式不正確,通常會發生這項錯誤。如果您確信未知的程式庫存在且應可解析,請務必透過以空格分隔的清單傳遞至 --files 旗標,正確傳遞依附程式庫的檔案。

fi-0052:找不到名稱

如果使用的名稱是 FIDL 編譯器找不到的,就會發生這項錯誤。

library test.bad.fi0052;

protocol Parser {
    Tokenize() -> (struct {
        tokens vector<string>;
    }) error ParsingError; // ParsingError doesn't exist.
};

如要修正這個問題,請移除找不到的名稱:

library test.good.fi0052a;

protocol Parser {
    Tokenize() -> (struct {
        tokens vector<string>;
    });
};

或定義找不到的名稱:

library test.good.fi0052b;

type ParsingError = flexible enum {
    UNEXPECTED_EOF = 0;
};

protocol Parser {
    Tokenize() -> (struct {
        tokens vector<string>;
    }) error ParsingError;
};

fi-0053:無法參照成員

如果參照的成員不是 bits 或 enum 項目,就會發生這項錯誤。

library test.bad.fi0053a;

type Person = struct {
    name string;
    birthday struct {
        year uint16;
        month uint8;
        day uint8;
    };
};

const JOHNS_NAME Person.name = "John Johnson"; // Cannot refer to member of struct 'Person'.
library test.bad.fi0053b;

type Person = struct {
    name string;
    birthday struct {
        year uint16;
        month uint8;
        day uint8;
    };
};

type Cat = struct {
    name string;
    age Person.birthday; // Cannot refer to member of struct 'Person'.
};

如要修正這項錯誤,請改用具名類型:

library test.good.fi0053a;

type Person = struct {
    name string;
    birthday struct {
        year uint16;
        month uint8;
        day uint8;
    };
};

const JOHNS_NAME string = "John Johnson";

或擷取成員的類型:

library test.good.fi0053b;

type Date = struct {
    year uint16;
    month uint8;
    day uint8;
};

type Person = struct {
    name string;
    birthday Date;
};

type Cat = struct {
    name string;
    age Date;
};

fi-0054:位元/列舉成員無效

如果參照的 enum 或 bits 成員先前未定義,就會發生這項錯誤。

library test.bad.fi0054;

type Enum = enum {
    foo_bar = 1;
};

const EXAMPLE Enum = Enum.FOO_BAR;

為避免發生這項錯誤,請確認您先前已為參照的成員值宣告值。這些值會區分大小寫。

library test.good.fi0054;

type Enum = enum {
    foo_bar = 1;
};

const EXAMPLE Enum = Enum.foo_bar;

fi-0055:參照已淘汰的項目

如果參照 type 或 const,但 @available 屬性不相容,就會發生這項錯誤。這通常發生在使用已淘汰的 types 或較新版本的 consts 時。

@available(added=1)
library test.bad.fi0055;

@available(added=1, deprecated=2, note="use Color instead")
alias RGB = array<uint8, 3>;

@available(added=2)
type Color = struct {
    r uint8;
    g uint8;
    b uint8;
    a uint8;
};

@available(added=3)
type Config = table {
    // RGB is deprecated in version 2.
    1: color RGB;
};

如要修正這項錯誤,請使用未淘汰的 type 或 const:

@available(added=1)
library test.good.fi0055;

@available(added=1, deprecated=2, note="use Color instead")
alias RGB = array<uint8, 3>;

@available(added=2)
type Color = struct {
    r uint8;
    g uint8;
    b uint8;
    a uint8;
};

@available(added=3)
type Config = table {
    // Using a non-deprecated type.
    1: color Color;
};

fi-0056:參照已淘汰的其他平台無效

如果參照其他平台的 type 或 const,但 @available 屬性不相容,就會發生這項錯誤。這通常發生在從較新版本使用已淘汰的 types 或 consts 時。

@available(platform="foo", added=1)
library test.bad.fi0056a;

@available(added=1, deprecated=2, note="use Color instead")
alias RGB = array<uint8, 3>;

@available(added=2)
type Color = struct {
    r uint8;
    g uint8;
    b uint8;
    a uint8;
};
@available(platform="bar", added=2)
library test.bad.fi0056b;

using test.bad.fi0056a;

@available(added=3)
type Config = table {
    // RGB is deprecated in version 2.
    1: color test.bad.fi0056a.RGB;
};

如要修正這項錯誤,請使用未淘汰的 type 或 const:

@available(platform="foo", added=1)
library test.good.fi0056a;

@available(added=1, deprecated=2, note="use Color instead")
alias RGB = array<uint8, 3>;

@available(added=2)
type Color = struct {
    r uint8;
    g uint8;
    b uint8;
    a uint8;
};
@available(platform="bar", added=2)
library test.good.fi0056b;

using test.good.fi0056a;

@available(added=2)
type Config = table {
    // Change to use a non-deprecated type.
    1: color test.good.fi0056a.Color;
};

fi-0057:包含循環

有許多情況可能會導致這個問題,但基本上都歸結於 FIDL 宣告以無法解析的方式參照自身。這類錯誤最簡單的形式,是類型或通訊協定在定義中直接參照自身:

library test.bad.fi0057c;

type MySelf = struct {
    me MySelf;
};

如果類型或通訊協定透過至少一個間接層級遞移參照自身,則可能會發生更複雜的失敗情況:

library test.bad.fi0057a;

type Yin = struct {
    yang Yang;
};

type Yang = struct {
    yin Yin;
};
library test.bad.fi0057b;

protocol Yin {
    compose Yang;
};

protocol Yang {
    compose Yin;
};

如要解決這項錯誤,請在包含循環中的某處新增信封 (也就是選用性),這樣就能在編碼/解碼時「中斷」循環:

library test.good.fi0057;

type MySelf = struct {
    me box<MySelf>;
};
library test.bad.fi0057d;

type MySelf = table {
    1: me MySelf;
};

未經信封拆解的遞迴類型無法編碼,因此不允許使用。在上述第一個範例中,編碼 MySelf 需要先編碼 MySelf 的例項,而這又需要編碼 MySelf 的例項,以此類推。解決這個問題的方法是透過選擇性在鏈結中加入「中斷」,使用者可以選擇編碼另一個 MySelf 巢狀例項,或編碼沒有其他資料的空值封包。

fi-0058:參照編譯器產生的酬載名稱

匿名方法酬載的名稱會由 FIDL 編譯器自動產生,因此產生的後端程式碼使用者可以視需要參照其代表的類型。不過,禁止在 *.fidl 檔案中參照這些類型:

library test.bad.fi0058;

protocol MyProtocol {
    strict MyInfallible(struct {
        in uint8;
    }) -> (struct {
        out int8;
    });
    strict MyFallible(struct {
        in uint8;
    }) -> (struct {
        out int8;
    }) error flexible enum {};
    strict -> MyEvent(struct {
        out int8;
    });
};

type MyAnonymousReferences = struct {
    a MyProtocolMyInfallibleRequest;
    b MyProtocolMyInfallibleResponse;
    c MyProtocolMyFallibleRequest;
    d MyProtocol_MyFallible_Result;
    e MyProtocol_MyFallible_Response;
    f MyProtocol_MyFallible_Error;
    g MyProtocolMyEventRequest;
};

如要直接參照酬載類型,請將酬載類型擷取至其專屬的具名類型宣告:

library test.good.fi0058;

type MyRequest = struct {
    in uint8;
};
type MyResponse = struct {
    out int8;
};
type MyError = flexible enum {};

protocol MyProtocol {
    strict MyInfallible(MyRequest) -> (MyResponse);
    strict MyFallible(MyRequest) -> (MyResponse) error MyError;
    strict -> MyEvent(MyResponse);
};

type MyAnonymousReferences = struct {
    a MyRequest;
    b MyResponse;
    c MyRequest;
    // There is no way to explicitly name the error result union.
    // d MyProtocol_MyFallible_Result;
    e MyResponse;
    f MyError;
    g MyResponse;
};

所有 FIDL 方法和事件都會保留 [PROTOCOL_NAME][METHOD_NAME]Request 名稱,用於匿名要求酬載。嚴格、可靠的雙向方法會額外保留 [PROTOCOL_NAME][METHOD_NAME]Response。如果雙向方法具有彈性或容易出錯,請改用:

  • [PROTOCOL_NAME]_[METHOD_NAME]_Result
  • [PROTOCOL_NAME]_[METHOD_NAME]_Response
  • [PROTOCOL_NAME]_[METHOD_NAME]_Error

基於歷史原因,這些名稱使用底線,與其他名稱不同。

fi-0059: Invalid constant type

並非所有類型都能用於 const 宣告:

library test.bad.fi0059;

const MY_CONST string:optional = "foo";

如果可以,請轉換為允許的類型:

library test.good.fi0059;

const MY_CONST string = "foo";

只有 FIDL 原始類型 (bool、int8、int16、int32、int64、uint8、uint16、uint32、uint64、float32、float64) 和非選用 string 類型,可用於 const 宣告的左側。

fi-0060:無法解析常數值

常數值必須可解析為已知值:

library test.bad.fi0060;

const MY_CONST bool = optional;

請確認使用的常數是有效值:

library test.good.fi0060;

const MY_CONST bool = true;

這項錯誤通常會伴隨其他錯誤,提供更多關於無法解析的預期常數性質的資訊。

fi-0061:非原始值的 OR 運算子

二進位 OR 運算子只能用於原始類型:

library test.bad.fi0061;

const HI string = "hi";
const THERE string = "there";
const OR_OP string = HI | THERE;

請改用 bits 列舉來表示要操作的資料:

library test.good.fi0061;

type MyBits = flexible bits {
    HI = 0x1;
    THERE = 0x10;
};
const OR_OP MyBits = MyBits.HI | MyBits.THERE;

fi-0062:不允許使用新類型

RFC-0052:類型別名和新類型中的新類型尚未完全實作,因此無法使用:

library test.bad.fi0062;

type Matrix = array<float64, 9>;

在此期間,您可以透過定義具有單一元素的結構體,來達到類似的效果:

library test.good.fi0062a;

type Matrix = struct {
    elements array<float64, 9>;
};

或者,您也可以定義別名,但請注意,與 newtype 不同,這不提供類型安全 (也就是說,它可以與基礎類型互換使用):

library test.good.fi0062b;

alias Matrix = array<float64, 9>;

fi-0063: Expected value but got type

const 宣告的右側必須解析為常數值,而非類型:

library test.bad.fi0063;

type MyType = struct {};
const MY_CONST uint32 = MyType;

確認右側是值:

library test.good.fi0063;

const MY_VALUE uint32 = 8;
const MY_CONST uint32 = MY_VALUE;

fi-0064:位元或列舉值類型不正確

在 const 宣告中使用 bits 或 enum 變體做為值時,bits/enum 值的類型必須與 const 宣告的左側相同:

library test.bad.fi0064;

type MyEnum = enum : int32 {
    VALUE = 1;
};
type OtherEnum = enum : int32 {
    VALUE = 5;
};
const MY_CONST MyEnum = OtherEnum.VALUE;

其中一個解決方法是變更 const 宣告的類型,使其與儲存的值相符:

library test.good.fi0064;

type MyEnum = enum : int32 {
    VALUE = 1;
};
type OtherEnum = enum : int32 {
    VALUE = 5;
};
const MY_CONST OtherEnum = OtherEnum.VALUE;

或者,您也可以選取其他值,以符合 const 宣告的類型:

library test.good.fi0064;

type MyEnum = enum : int32 {
    VALUE = 1;
};
type OtherEnum = enum : int32 {
    VALUE = 5;
};
const MY_CONST MyEnum = MyEnum.VALUE;

fi-0065:無法將值轉換為預期類型

常數值的類型必須與使用位置相符。

這項錯誤最常見的原因是 const 宣告的值與其宣告的類型不符:

library test.bad.fi0065a;

const MY_CONST bool = "foo";

如果正確定義的 const 值用於基礎類型無效的位置,仍可能發生問題:

library test.bad.fi0065b;

const ONE uint8 = 0x0001;
const TWO_FIFTY_SIX uint16 = 0x0100;
const TWO_FIFTY_SEVEN uint8 = ONE | TWO_FIFTY_SIX;

此外,FIDL 的官方會根據結構定義檢查引數。由於這些引數本身是常數值,因此可能會發生相同的類型不符:

library test.bad.fi0065c;

protocol MyProtocol {
    @selector(3840912312901827381273)
    MyMethod();
};

在上述所有情況下,解決方法是在接受 const 值的位置,只使用預期類型的值。上述情況分別會變成:

library test.good.fi0065a;

const MY_CONST string = "foo";
library test.good.fi0065b;

const ONE uint8 = 0x0001;
const TWO_FIFTY_SIX uint16 = 0x0100;
const TWO_FIFTY_SEVEN uint16 = ONE | TWO_FIFTY_SIX;
library test.good.fi0065c;

protocol MyProtocol {
    @selector("MyOldMethod")
    MyMethod();
};

fi-0066:常數溢位類型

常數值不得超出其基礎類型固有的範圍:

library test.bad.fi0066;

const NUM uint64 = -42;

如要修正問題,可以將值變更為符合類型範圍的值:

library test.good.fi0066a;

const NUM uint64 = 42;

或者,也可以變更為可容納目前溢位值的類型:

library test.good.fi0066b;

const NUM int64 = -42;

這項錯誤僅與 FIDL 的數值類型有關,所有這類類型都有可能溢位。這些範圍來自 C++ std::numeric_limits 介面,如下所示:

類型 最小值 上限
int8 -128 127
int16 32768 32767
int32 2147483648 2147483647
int64 9223372036854775808 9223372036854775807
uint8 0 255
uint16 0 65536
uint32 0 4294967295
uint64 0 18446744073709551615
float32 -3.40282e+38 3.40282e+38
float64 -1.79769e+308 1.79769e+308

fi-0067:位元成員必須是 2 的乘冪

bits 宣告中所有成員的值不得為非 2 的乘冪:

library test.bad.fi0067;

type NonPowerOfTwo = bits : uint64 {
    THREE = 3;
};

成員值應一律為 2 的冪:

library test.good.fi0067a;

type Fruit = bits : uint64 {
    ORANGE = 1;
    APPLE = 2;
    BANANA = 4;
};

如要避免受到這項限制影響,最簡單的方法就是使用位元遮罩,而非十進位數字,來表示位元成員值:

library test.good.fi0067b;

type Life = bits {
    A = 0b000010;
    B = 0b001000;
    C = 0b100000;
};

bits 建構函式代表位元陣列。這是表示布林值旗標序列最節省記憶體的方式。由於 bits 宣告的每個成員都會對應至其基礎記憶體的特定位元,因此用於該對應的值必須明確識別要指派的無正負號整數中的特定位元。

fi-0068:彈性列舉含有保留的未知值

如果定義的列舉成員值與保留的未知值衝突,就會發生這項錯誤。

彈性列舉可能包含 FIDL 結構定義未知的值。此外,彈性列舉一律會保留部分值,並視為未知。根據預設,該值是該列舉基礎整數類型可表示的最大數值 (例如 uint8 的 255)。

library test.bad.fi0068;

type Foo = flexible enum : uint8 {
    ZERO = 0;
    ONE = 1;
    MAX = 255;
};

如要修正錯誤,請移除成員或變更其值:

library test.good.fi0068a;

type Foo = flexible enum : uint8 {
    ZERO = 0;
    ONE = 1;
};
library test.good.fi0068b;

type Foo = flexible enum : uint8 {
    ZERO = 0;
    ONE = 1;
    MAX = 254;
};

最後,如果您在將 strict 列舉轉換為 flexible 列舉時遇到這項錯誤,可以使用 @unknown 屬性,將特定成員的數值指定為未知值。請參閱「@unknown」。

fi-0069:位元必須使用無正負號整數子類型

禁止使用有正負號的數字做為 bits 宣告的基礎類型:

library test.bad.fi0069;

type Fruit = bits : int64 {
    ORANGE = 1;
    APPLE = 2;
    BANANA = 4;
};

請改用下列任一項目:uint8、uint16、uint32 或 uint64:

library test.good.fi0069;

type Fruit = bits : uint64 {
    ORANGE = 1;
    APPLE = 2;
    BANANA = 4;
};

enum 宣告允許有正負號和無正負號的整數 (請參閱 fi-0070),bits 宣告則只允許後者。這是因為每個 bits 成員都必須代表位元陣列中的特定基礎位元 (這也是 fi-0067 存在的原因)。最簡潔的表示方式是單一不帶正負號的整數。無正負號整數的二進位表示法會直接對應到單一位元 (2 的索引次方),而有正負號整數中的負數則幾乎一律會選取多個位元,這是因為二補數表示法的機制。

fi-0070:列舉必須使用整數子類型

禁止使用非整數數值 float32 或 float64 做為 enum 宣告的基礎類型:

library test.bad.fi0070;

type MyEnum = enum : float64 {
    ONE_POINT_FIVE = 1.5;
};

請改用下列任一項目:int8、int16、int32、int64、uint8、uint16、uint32 或 uint64:

library test.good.fi0070;

type MyEnum = enum : uint64 {
    ONE = 1;
};

fi-0071:嚴格列舉成員不允許使用未知屬性

strict enum 不得有任何成員加上 @unknown 屬性註解:

library test.bad.fi0071;

type MyEnum = strict enum : int8 {
    @unknown
    UNKNOWN = 0;
    FOO = 1;
    MAX = 127;
};

如要繼續使用 @unknown 屬性,請改用 flexible enum:

library test.good.fi0071a;

type MyEnum = flexible enum : int8 {
    @unknown
    UNKNOWN = 0;
    FOO = 1;
    MAX = 127;
};

否則,只要移除屬性,即可保留 strict enum:

library test.good.fi0071;

type MyEnum = strict enum : int8 {
    UNKNOWN = 0;
    FOO = 1;
    MAX = 127;
};

@unknown 屬性的用途是順暢轉換,從具有使用者定義未知值的 strict enum (例如這個),轉換為具有 FIDL 已知並處理的未知值的 flexible enum。在上述範例中,這項功能可用於從第二種正確用法轉換為第一種。

fi-0072:只有列舉成員可以包含未知屬性

禁止使用 @unknown 屬性裝飾多個 enum 成員:

library test.bad.fi0072;

type MyEnum = flexible enum : uint8 {
    @unknown
    UNKNOWN = 0;
    @unknown
    OTHER = 1;
};

請只選擇並標註用作網域專屬「未知」值的成員:

library test.good.fi0071a;

type MyEnum = flexible enum : int8 {
    @unknown
    UNKNOWN = 0;
    OTHER = 1;
};

@unknown 屬性的用途是順暢轉換,從具有使用者定義未知值的 strict enum (如下所示),轉換為具有 FIDL 已知並處理未知值的 flexible enum:

library test.good.fi0072;

type MyEnum = strict enum : int8 {
    UNKNOWN = 0;
    OTHER = 1;
};
library test.good.fi0071a;

type MyEnum = flexible enum : int8 {
    @unknown
    UNKNOWN = 0;
    OTHER = 1;
};

fi-0073:組合非通訊協定

compose 陳述式只能使用通訊協定:

library test.bad.fi0073;

type MyStruct = struct {};

protocol MyProtocol {
    compose MyStruct;
};

請確認您參照的名稱指向通訊協定:

library test.good.fi0073;

protocol MyOtherProtocol {};

protocol MyProtocol {
    compose MyOtherProtocol;
};

fi-0074:方法酬載使用的版面配置無效

只有 struct、table 或 union 版面配置可用於描述方法酬載:

library test.bad.fi0074;

protocol MyProtocol {
    MyMethod(enum {
        FOO = 1;
    });
};

請改用下列其中一種版面配置:

library test.good.fi0074;

protocol MyProtocol {
    MyMethod(struct {
        foo bool;
    });
};

fi-0075: 方法酬載使用的原始類型無效

原始物件無法做為方法酬載:

library test.bad.fi0075;

protocol MyProtocol {
    MyMethod(uint32);
};

請改用 struct、table 或 union 版面配置類型:

library test.good.fi0075;

protocol MyProtocol {
    MyMethod(struct {
        wrapped_in_struct uint32;
    });
};

如果所需的酬載只是原始值,且不需考量未來演變,則將值包裝在 struct 版面配置中,會產生與所需值本身大小相同的酬載。

fi-0076

fi-0077:互動酬載不得為空白結構

方法或事件中的酬載不得為空白結構體:

library test.bad.fi0077a;

protocol Test {
    MyMethod(struct {}) -> (struct {});
};
library test.bad.fi0077b;

protocol Test {
    -> MyEvent(struct {});
};

如要表示特定要求/回應不含任何資訊,請刪除空白結構,並在該位置保留 ():

library test.good.fi0077a;

protocol Test {
    MyMethod() -> ();
};
library test.good.fi0077b;

protocol Test {
    -> MyEvent();
};

空白結構體無法擴充,且在傳輸時會占用 1 個位元組。由於 FIDL 支援不含酬載的互動,因此以這種方式使用空白結構體是多餘且效率較低的做法。因此不允許使用。

fi-0078

fi-0079

fi-0080:產生的序數值為零

這個錯誤不應發生。如果成功,恭喜你,你可能破解了 SHA-256!

言歸正傳,如果 fidlc 編譯器產生 0 的序數值,就會發生這項錯誤。這種情況不應發生,因此如果發生,您可能在 FIDL 編譯器中發現錯誤。如果發生這種情況,請透過Issue Tracker回報問題。

fi-0081: Duplicate method ordinal

如果使用 @selector 屬性,讓兩個方法名稱產生相同的序數,通常就會發生這項錯誤。

library test.bad.fi0081;

protocol Parser {
    ParseLine();

    // Multiple methods with the same ordinal...
    @selector("ParseLine")
    ParseOneLine();
};

如要修正這個問題,請更新方法名稱或選取器,避免發生衝突。

library test.good.fi0081;

protocol Parser {
    ParseLine();

    @selector("Parse1Line")
    ParseOneLine();
};

如果發生 SHA-256 衝突,也可能發生這個錯誤,但這種情況基本上不可能發生。如果您確定選取器沒有問題,但仍遇到這項錯誤,可能是 FIDL 編譯器有錯誤。如果發生這種情況,請透過Issue Tracker回報問題。

fi-0082:選取器值無效

如果 @selector 的值無效,就會發生這個錯誤。最常見的原因是輸入錯誤。選取器必須是獨立方法名稱或完整方法名稱。

library test.bad.fi0082;

protocol Parser {
    @selector("test.old.fi0082.Parser.Parse")
    Parse();
};

如要修正這個問題,請將選取器更新為有效的獨立方法名稱,或完整限定方法名稱:

library test.good.fi0082;

protocol Parser {
    @selector("test.old.fi0082/Parser.Parse")
    Parse();
};

fi-0083:fuchsia.io 必須使用明確的序數

FIDL 編譯器過去會自動將 fuchsia.io 序數重新命名為 fuchsia.io1。這項神奇功能旨在讓方法的 io2 版本具有「一般」序數,以便更輕鬆地遷移至 fuchsia.io2。不過,這個系統最終有點太過神奇,因此現在必須手動提供 fuchsia.io 的序數。

library fuchsia.io;

protocol SomeProtocol {
    SomeMethod();
};

如要修正這個問題,請使用 fuchsia.io1 做為程式庫名稱,手動提供選取器,允許 fuchsia.io 名稱用於 io2。

library fuchsia.io;

protocol SomeProtocol {
    @selector("fuchsia.io1/SomeProtocol.SomeMethod")
    SomeMethod();
};

fi-0084:方法酬載結構體不允許預設成員

用做方法酬載的結構體不得指定預設成員:

library test.bad.fi0084;

type MyStruct = struct {
    @allow_deprecated_struct_defaults
    a bool = false;
};

protocol MyProtocol {
    MyMethod(MyStruct) -> (MyStruct);
};

從相關的 struct 宣告中移除預設成員:

library test.good.fi0084;

type MyStruct = struct {
    a bool;
};

protocol MyProtocol {
    MyMethod(MyStruct) -> (MyStruct);
};

fi-0085

fi-0086

fi-0087

fi-0088:服務成員不得為選用

將服務成員標示為 optional 時,就會發生這項錯誤。服務成員一律為選用,因此無法標示為 optional。

library test.bad.fi0088;

protocol Sorter {
    Sort(struct {
        input vector<int32>;
    }) -> (struct {
        output vector<int32>;
    });
};

service SortService {
    quicksort client_end:<Sorter, optional>;
    mergesort client_end:<Sorter, optional>;
};

如要修正這個問題,請移除選用子句:

library test.good.fi0088;

protocol Sorter {
    Sort(struct {
        input vector<int32>;
    }) -> (struct {
        output vector<int32>;
    });
};

service SortService {
    quicksort client_end:Sorter;
    mergesort client_end:Sorter;
};

fi-0089

fi-0090

fi-0091: Invalid struct member type

如果嘗試為不支援的類型設定預設 struct 值,就會發生這項錯誤。只有數值和布林值類型可以設定預設 struct 值。

library test.bad.fi0091;

type Person = struct {
    @allow_deprecated_struct_defaults
    name string:optional = "";
};

如要修正這個問題,請移除預設值:

library test.good.fi0091;

type Person = struct {
    @allow_deprecated_struct_defaults
    name string:optional;
};

fi-0092: Table ordinal too large

FIDL 資料表序數不得大於 64:

library test.bad.fi0092;

type Table64thField = table {
    1: x int64;
};

type Example = table {
    1: v1 int64;
    2: v2 int64;
    3: v3 int64;
    4: v4 int64;
    5: v5 int64;
    6: v6 int64;
    7: v7 int64;
    8: v8 int64;
    9: v9 int64;
    10: v10 int64;
    11: v11 int64;
    12: v12 int64;
    13: v13 int64;
    14: v14 int64;
    15: v15 int64;
    16: v16 int64;
    17: v17 int64;
    18: v18 int64;
    19: v19 int64;
    20: v20 int64;
    21: v21 int64;
    22: v22 int64;
    23: v23 int64;
    24: v24 int64;
    25: v25 int64;
    26: v26 int64;
    27: v27 int64;
    28: v28 int64;
    29: v29 int64;
    30: v30 int64;
    31: v31 int64;
    32: v32 int64;
    33: v33 int64;
    34: v34 int64;
    35: v35 int64;
    36: v36 int64;
    37: v37 int64;
    38: v38 int64;
    39: v39 int64;
    40: v40 int64;
    41: v41 int64;
    42: v42 int64;
    43: v43 int64;
    44: v44 int64;
    45: v45 int64;
    46: v46 int64;
    47: v47 int64;
    48: v48 int64;
    49: v49 int64;
    50: v50 int64;
    51: v51 int64;
    52: v52 int64;
    53: v53 int64;
    54: v54 int64;
    55: v55 int64;
    56: v56 int64;
    57: v57 int64;
    58: v58 int64;
    59: v59 int64;
    60: v60 int64;
    61: v61 int64;
    62: v62 int64;
    63: v63 int64;
    // The 64th field of a table must be another table, otherwise it will cause
    // fi-0093: Max Ordinal In Table Must Be Table.
    64: v64 Table64thField;
    65: v65 int64;
};

為了允許超過 64 個序數,FIDL 要求資料表的最後一個欄位必須是另一個資料表。超過 64 個的資料表欄位必須放在巢狀資料表中。

library test.good.fi0092;

type Table64thField = table {
    1: x int64;
    // Any fields beyond 64 of table Example must be move to the nested table in
    // ordinal 64 of Example.
    2: v65 int64;
};

type Example = table {
    1: v1 int64;
    2: v2 int64;
    3: v3 int64;
    4: v4 int64;
    5: v5 int64;
    6: v6 int64;
    7: v7 int64;
    8: v8 int64;
    9: v9 int64;
    10: v10 int64;
    11: v11 int64;
    12: v12 int64;
    13: v13 int64;
    14: v14 int64;
    15: v15 int64;
    16: v16 int64;
    17: v17 int64;
    18: v18 int64;
    19: v19 int64;
    20: v20 int64;
    21: v21 int64;
    22: v22 int64;
    23: v23 int64;
    24: v24 int64;
    25: v25 int64;
    26: v26 int64;
    27: v27 int64;
    28: v28 int64;
    29: v29 int64;
    30: v30 int64;
    31: v31 int64;
    32: v32 int64;
    33: v33 int64;
    34: v34 int64;
    35: v35 int64;
    36: v36 int64;
    37: v37 int64;
    38: v38 int64;
    39: v39 int64;
    40: v40 int64;
    41: v41 int64;
    42: v42 int64;
    43: v43 int64;
    44: v44 int64;
    45: v45 int64;
    46: v46 int64;
    47: v47 int64;
    48: v48 int64;
    49: v49 int64;
    50: v50 int64;
    51: v51 int64;
    52: v52 int64;
    53: v53 int64;
    54: v54 int64;
    55: v55 int64;
    56: v56 int64;
    57: v57 int64;
    58: v58 int64;
    59: v59 int64;
    60: v60 int64;
    61: v61 int64;
    62: v62 int64;
    63: v63 int64;
    64: v64 Table64thField;
};

資料表中的每個欄位都會產生 FIDL 信封的負擔,以便將欄位設為選填。這樣一來,資料表的每個欄位都可以存在或不存在,而且可以透過新增或移除欄位來演進資料表,但代價是記憶體負擔比 struct 大得多。

一般來說,只要避免在資料表中加入精細的小型欄位,就能避免這項錯誤並減少負擔。您可以將預期需要同時新增或移除的元素分組為 struct,並將這些元素做為資料表的欄位。這會降低負擔,並避免序數用盡,但會犧牲部分可演進性。

RFC-0132:FIDL 資料表大小限制將此視為錯誤,目的是防止使用者不小心產生超大資料表的負擔。這項額外費用在結構定義中並不明顯,尤其是在只有少數欄位 (具有大型序數),或是有許多欄位但一次只使用少數欄位時。

fi-0093:資料表中的最大序數必須是資料表

FIDL 表格中第 64 個成員的類型本身必須是表格:

library test.bad.fi0093;

type Example = table {
    1: v1 int64;
    2: v2 int64;
    3: v3 int64;
    4: v4 int64;
    5: v5 int64;
    6: v6 int64;
    7: v7 int64;
    8: v8 int64;
    9: v9 int64;
    10: v10 int64;
    11: v11 int64;
    12: v12 int64;
    13: v13 int64;
    14: v14 int64;
    15: v15 int64;
    16: v16 int64;
    17: v17 int64;
    18: v18 int64;
    19: v19 int64;
    20: v20 int64;
    21: v21 int64;
    22: v22 int64;
    23: v23 int64;
    24: v24 int64;
    25: v25 int64;
    26: v26 int64;
    27: v27 int64;
    28: v28 int64;
    29: v29 int64;
    30: v30 int64;
    31: v31 int64;
    32: v32 int64;
    33: v33 int64;
    34: v34 int64;
    35: v35 int64;
    36: v36 int64;
    37: v37 int64;
    38: v38 int64;
    39: v39 int64;
    40: v40 int64;
    41: v41 int64;
    42: v42 int64;
    43: v43 int64;
    44: v44 int64;
    45: v45 int64;
    46: v46 int64;
    47: v47 int64;
    48: v48 int64;
    49: v49 int64;
    50: v50 int64;
    51: v51 int64;
    52: v52 int64;
    53: v53 int64;
    54: v54 int64;
    55: v55 int64;
    56: v56 int64;
    57: v57 int64;
    58: v58 int64;
    59: v59 int64;
    60: v60 int64;
    61: v61 int64;
    62: v62 int64;
    63: v63 int64;
    64: v64 int64;
};

如果使用者需要新增第 64 個成員,應建立另一個資料表來存放第 64 個及後續成員,並將該成員放入該資料表中:

library test.good.fi0093;

type Table64thField = table {
    1: x int64;
};

type Example = table {
    1: v1 int64;
    2: v2 int64;
    3: v3 int64;
    4: v4 int64;
    5: v5 int64;
    6: v6 int64;
    7: v7 int64;
    8: v8 int64;
    9: v9 int64;
    10: v10 int64;
    11: v11 int64;
    12: v12 int64;
    13: v13 int64;
    14: v14 int64;
    15: v15 int64;
    16: v16 int64;
    17: v17 int64;
    18: v18 int64;
    19: v19 int64;
    20: v20 int64;
    21: v21 int64;
    22: v22 int64;
    23: v23 int64;
    24: v24 int64;
    25: v25 int64;
    26: v26 int64;
    27: v27 int64;
    28: v28 int64;
    29: v29 int64;
    30: v30 int64;
    31: v31 int64;
    32: v32 int64;
    33: v33 int64;
    34: v34 int64;
    35: v35 int64;
    36: v36 int64;
    37: v37 int64;
    38: v38 int64;
    39: v39 int64;
    40: v40 int64;
    41: v41 int64;
    42: v42 int64;
    43: v43 int64;
    44: v44 int64;
    45: v45 int64;
    46: v46 int64;
    47: v47 int64;
    48: v48 int64;
    49: v49 int64;
    50: v50 int64;
    51: v51 int64;
    52: v52 int64;
    53: v53 int64;
    54: v54 int64;
    55: v55 int64;
    56: v56 int64;
    57: v57 int64;
    58: v58 int64;
    59: v59 int64;
    60: v60 int64;
    61: v61 int64;
    62: v62 int64;
    63: v63 int64;
    64: v64 Table64thField;
};

RFC-0132:FIDL 表格大小限制中,完整說明瞭這項規定的原因和動機。簡而言之,FIDL 表格允許的欄位數量必須受到相對限制,否則一次只使用幾個欄位的表格編碼形式,會出現無法接受的大量無效空間 (每個省略的成員 16 個位元組)。

為了讓使用者在資料表中加入超過 64 個欄位,FIDL 會強制將最後一個序數保留給包含額外欄位的「接續資料表」。如果在這個位置使用任何其他類型,資料表日後就無法擴充。

fi-0094:資料表成員序號重複

table 宣告中成員使用的序數不得重複:

library test.bad.fi0094;

type MyTable = table {
    1: my_field string;
    1: my_other_field uint32;
};

視需要遞增序數,確保宣告的所有成員都有不重複的序數:

library test.good.fi0094a;

type MyTable = table {
    1: my_field string;
    2: my_other_field uint32;
};

或者,您也可以移除其中一個名稱重複的成員:

library test.good.fi0094b;

type MyTable = table {
    1: my_field string;
};

序數用於識別傳輸中的欄位。如果兩個成員共用一個序數,就無法可靠地判斷解碼 FIDL 訊息時參照的是哪個欄位。

fi-0095

fi-0096

fi-0097:組合成員序數重複

union 宣告中成員使用的序數不得重複:

library test.bad.fi0097;

type MyUnion = strict union {
    1: my_variant string;
    1: my_other_variant int32;
};

視需要遞增序數,確保宣告的所有成員都有不重複的序數:

library test.good.fi0097a;

type MyUnion = strict union {
    1: my_variant string;
    2: my_other_variant int32;
};

或者,您也可以移除其中一個名稱重複的成員:

library test.good.fi0097b;

type MyUnion = strict union {
    1: my_variant string;
};

序數用於識別傳輸中的變體。如果兩個成員共用一個序數,就無法在解碼 FIDL 訊息時,可靠地判斷參照的是哪個變體。

fi-0098

fi-0099

fi-0100

fi-0101:無法解析的大小限制

套用至 vector 或 string 類型定義的大小限制,必須是 uint32 類型的有效值:

library test.bad.fi0101a;

alias MyBoundedOptionalVector = vector<uint32>:<"255", optional>;
library test.bad.fi0101b;

alias MyBoundedOptionalVector = vector<uint32>:<uint8, optional>;

請確認下列事項:

library test.good.fi0101;

alias MyBoundedOptionalVector = vector<uint32>:<255, optional>;

fi-0102:無法解析成員值

bits 和 enum 宣告的成員必須是指定子類型的可解析值:

library test.bad.fi0102;

type Fruit = bits : uint64 {
    ORANGE = 1;
    APPLE = 2;
    BANANA = -4;
};

請確認所有值都與宣告的基礎類型相符:

library test.good.fi0102;

type Fruit = bits : uint64 {
    ORANGE = 1;
    APPLE = 2;
    BANANA = 4;
};

fi-0103:無法解析 struct 預設值

struct 宣告成員的預設值必須與各自成員的宣告類型相符:

library test.bad.fi0103;

type MyEnum = enum : int32 {
    A = 1;
};

type MyStruct = struct {
    @allow_deprecated_struct_defaults
    field MyEnum = 1;
};

確認值與宣告的類型相符:

library test.good.fi0103;

type MyEnum = enum : int32 {
    A = 1;
};

type MyStruct = struct {
    @allow_deprecated_struct_defaults
    field MyEnum = MyEnum.A;
};

fi-0104:無法解析的屬性引數

官方 FIDL 屬性的引數值,不得違反屬性結構定義對該引數的預期:

library test.bad.fi0104;

type MyStruct = struct {
    my_field @generated_name(true) struct {};
};

確認做為屬性引數的值類型正確:

library test.good.fi0104;

type MyStruct = struct {
    my_field @generated_name("my_inner_type") struct {};
};

fi-0105

fi-0106

fi-0107:成員值重複

bits 和 enum 宣告都不能有相同值的成員:

library test.bad.fi0107;

type Fruit = flexible enum {
    ORANGE = 1;
    APPLE = 1;
};

將成員值全部變更為不重複的值:

library test.good.fi0107a;

type Fruit = flexible enum {
    ORANGE = 1;
    APPLE = 2;
};

或者,您也可以移除其中一個重複的成員:

library test.good.fi0107b;

type Fruit = flexible enum {
    ORANGE = 1;
};

fi-0108

fi-0109

fi-0110:包含類型的資源必須標示為資源

如果某個類型包含控制代碼 (無論是直接包含,還是透過遞移方式包含另一個含有控制代碼的類型),則必須將該類型指定為 resource,才能宣告該類型:

library test.bad.fi0110;

using zx;

type Foo = struct {
    handle zx.Handle;
};

有兩種可能的解決方法。第一種方法是使用資源修飾符,為有問題的宣告加上註解:

library test.good.fi0110a;

using zx;

type Foo = resource struct {
    handle zx.Handle;
};

或者,您也可以選擇完全移除包含 resource 的類型,這樣就不需要在擁有宣告上使用修飾符:

library test.good.fi0110b;

type Foo = struct {
    value uint32;
};

如要瞭解新增 resource 修飾符的原因和動機,以及這項錯誤強制執行的使用模式「感染」性質,請參閱 RFC-0057:預設無控制代碼。

fi-0111:內嵌大小超過上限

不允許內嵌大小為 64 KiB 以上的 FIDL 類型:

library test.bad.fi0111;

type MyStruct = struct {
    numbers array<uint8, 65536>;
};

請改為確保類型的內嵌大小小於 64 KiB。在這種情況下,我們可以調整陣列邊界:

library test.good.fi0111;

type MyStruct = struct {
    numbers array<uint8, 65535>;
};

這項限制是基於效能考量。這表示編碼器和解碼器可以假設大小和偏移量符合無正負號 16 位元整數。

除非您使用大型陣列或深度巢狀結構,否則在實際操作中不太可能遇到這種情況。大多數 FIDL 建構 (例如字串、向量、資料表和聯集) 都使用行外儲存空間,不計入個別內嵌大小。

fi-0112:服務成員不是client_end

服務成員只能是用戶端,不能是其他類型:

library test.bad.fi0112;

protocol Calculator {};

service Service {
    calculator server_end:Calculator;
};

如要修正錯誤,請確認成員的格式為 client_end:P (適用於某些通訊協定 P):

library test.good.fi0112;

protocol Calculator {};

service Service {
    calculator client_end:Calculator;
};

服務是通訊協定例項的集合,而非一般用途的資料結構,因此不適合允許任意類型。

fi-0113:服務中的運輸方式不符

FIDL 服務不得包含使用不同傳輸方式的通訊協定:

library test.bad.fi0113;

protocol ChannelProtocol {};

@transport("Driver")
protocol DriverProtocol {};

service SomeService {
    a client_end:ChannelProtocol;
    b client_end:DriverProtocol;
};

請改為每個傳輸方式使用個別服務:

library test.good.fi0113;

protocol ChannelProtocol {};

@transport("Driver")
protocol DriverProtocol {};

service ChannelService {
    protocol client_end:ChannelProtocol;
};

service DriverService {
    protocol client_end:DriverProtocol;
};

請注意,服務是 FIDL 中尚未完成的功能。這些 API 最初是在 RFC-0041:支援統一服務和裝置中設計。如要瞭解截至 2022 年 10 月的狀態,請參閱 https://fxbug.dev/42160684。

fi-0114:組合通訊協定過於開放

通訊協定無法組成比自身更開放的其他通訊協定:

library test.bad.fi0114;

open protocol Composed {};

ajar protocol Composing {
    compose Composed;
};

如要修正這個問題,可以提高撰寫通訊協定的開放程度,也就是從 closed 變更為 ajar,或從 ajar 變更為 open:

library test.good.fi0114a;

open protocol Composed {};

open protocol Composing {
    compose Composed;
};

或者,您也可以降低組合通訊協定的開放程度,也就是從 open 變更為 ajar,或從 ajar 變更為 closed:

library test.good.fi0114b;

ajar protocol Composed {};

ajar protocol Composing {
    compose Composed;
};

這項規則之所以存在,是因為通訊協定的開放性會限制其允許包含的方法類型。舉例來說,半開放通訊協定不能包含彈性的雙向方法,但開放通訊協定可以,因此半開放通訊協定不適合組成開放通訊協定。

如要進一步瞭解通訊協定修飾符,請參閱「RFC-0138:處理未知互動」。

fi-0115:彈性的雙向方法需要開放通訊協定

封閉式和半開式通訊協定不得包含彈性雙向方法:

library test.bad.fi0115;

ajar protocol Protocol {
    flexible Method() -> ();
};

請改為標記雙向方法 strict,而非 flexible:

library test.good.fi0115a;

ajar protocol Protocol {
    strict Method() -> ();
};

或者,您也可以將通訊協定標示為 open,而非 closed 或 ajar:

library test.good.fi0115b;

open protocol Protocol {
    flexible Method() -> ();
};

之所以會發生這個錯誤,是因為 closed (或 ajar) 修飾符的用途是確保方法不含任何彈性 (雙向) 方法。首次建立通訊協定時,請根據您需要的演進屬性,仔細考慮是否應關閉、半開或開放。

如要進一步瞭解通訊協定修飾符,請參閱「RFC-0138:處理未知互動」。

fi-0116:彈性單向方法需要 ajar 或 open 通訊協定

封閉式通訊協定不得包含彈性單向方法:

library test.bad.fi0116;

closed protocol Protocol {
    flexible Method();
};

請改為將單向方法標示為 strict,而非 flexible:

library test.good.fi0116;

closed protocol Protocol {
    strict Method();
};

或者,請將通訊協定標示為 ajar 或 open,而非 closed:

library test.good.fi0116;

ajar protocol Protocol {
    flexible Method();
};

之所以會發生這項錯誤,是因為 closed 修飾符的用途是確保方法不含任何彈性方法。首次建立通訊協定時,請根據您需要的可演化性屬性,仔細考慮是否應關閉、半開或開啟。

如要進一步瞭解通訊協定修飾符,請參閱「RFC-0138:處理未知互動」。

fi-0117:控制代碼用於不相容的傳輸

通訊協定只能參照與其傳輸方式相容的控制代碼。舉例來說,透過 Zircon 管道傳輸的通訊協定無法參照 Fuchsia Driver Framework 控制代碼:

library test.bad.fi0117;

using fdf;

protocol Protocol {
    Method(resource struct {
        h fdf.handle;
    });
};

請改用與通訊協定傳輸方式相容的控制代碼:

library test.good.fi0117a;

using zx;

protocol Protocol {
    Method(resource struct {
        h zx.Handle;
    });
};

或者,您也可以變更通訊協定的傳輸方式,使其與控制代碼相符:

library test.good.fi0117b;

using fdf;

@transport("Driver")
protocol Protocol {
    Method(resource struct {
        h fdf.handle;
    });
};

fi-0118:傳輸端用於不相容的傳輸

通訊協定只能參照相同傳輸通訊協定的傳輸端 (client_end 和 server_end)。舉例來說,使用 Syscall 傳輸的通訊協定,無法參照使用 Driver 傳輸的通訊協定用戶端:

library test.bad.fi0118;

@transport("Driver")
protocol DriverProtocol {};

@transport("Syscall")
protocol P {
    M(resource struct {
        s client_end:DriverProtocol;
    });
};

如要修正錯誤,請移除傳輸端成員:

library test.good.fi0118;

@transport("Driver")
protocol DriverProtocol {};

@transport("Syscall")
protocol Protocol {
    M();
};

fi-0119

fi-0120:屬性位置無效

部分官方屬性僅適用於特定位置。舉例來說,@selector 屬性只能用於方法:

library test.bad.fi0120a;

@selector("Nonsense")
type MyUnion = union {
    1: hello uint8;
};

如要修正錯誤,請移除屬性:

library test.good.fi0120a;

type MyUnion = union {
    1: hello uint8;
};

如果您打算以支援的方式使用屬性,但將屬性放在錯誤的位置,也可能會遇到這項錯誤。例如,@generated_name 屬性無法直接套用至成員:

library test.bad.fi0120a;

@selector("Nonsense")
type MyUnion = union {
    1: hello uint8;
};

而是應該放在成員的匿名版面配置之前:

library test.good.fi0120a;

type MyUnion = union {
    1: hello uint8;
};

fi-0121:已淘汰的屬性

部分官方屬性已淘汰,不應再使用:

library test.bad.fi0121;

@example_deprecated_attribute
type MyStruct = struct {};

修正方式取決於屬性淘汰的原因。舉例來說,錯誤訊息可能會建議改用其他屬性。在這種情況下,我們只要移除屬性即可:

library test.good.fi0121;

type MyStruct = struct {};

fi-0122:屬性名稱重複

元素不得有多個同名屬性:

library test.bad.fi0122;

@custom_attribute("first")
@custom_attribute("second")
type Foo = struct {};

請改為只指定每個屬性一次:

library test.good.fi0122;

@custom_attribute("first")
type Foo = struct {};

fi-0123:標準屬性名稱重複

元素不得有多個標準名稱相同的屬性:

library test.bad.fi0123;

@custom_attribute("first")
@CustomAttribute("second")
type Foo = struct {};

雖然 custom_attribute 和 CustomAttribute 看起來不同,但兩者都以標準名稱 custom_attribute 表示。將原始名稱轉換為 snake_case,即可取得標準名稱。

如要修正錯誤,請為每個屬性指定正規化後不重複的名稱。

library test.good.fi0123;

@custom_attribute("first")
@AnotherCustomAttribute("first")
type Foo = struct {};

如要進一步瞭解 FIDL 為何要求宣告必須具有不重複的標準名稱,請參閱 fi-0035。

fi-0124:自訂屬性引數必須是字串或布林值

使用者定義 FIDL 屬性的引數僅限字串或布林值類型:

library test.bad.fi0124;

@my_custom_attr(foo=1, bar=2.3)
type MyStruct = struct {};
library test.good.fi0124;

@my_custom_attr(foo=true, bar="baz")
type MyStruct = struct {};

與官方屬性不同,編譯器不知道使用者定義屬性的結構定義。因此,編譯器無法推斷任何指定數值引數的類型,例如 2 是 int8、uint64 還是 float32?編譯器無法得知。

這個問題的可能解決方案,是在 JSON IR 中為這類模稜兩可的情況實作第一類 numeric 類型。不過,由於自訂屬性引數是目前唯一已知的用途,因此這項功能並非優先處理項目。

fi-0125:不得為屬性引數命名

使用採用單一引數的官方屬性時,您無法為該引數命名:

library test.bad.fi0125;

@transport(value="Driver")
protocol Foo {};

請改為傳遞引數,但不指定名稱:

library test.good.fi0125;

@discoverable(name="example.Bar")
protocol Foo {};

FIDL 會強制執行這項規定,讓屬性更簡潔一致。在幕後,引數名稱會推斷為 value (這會顯示在 JSON IR 中),因為這是屬性採用的唯一引數。

fi-0126:必須為屬性引數命名

使用接受多個引數的官方屬性時,無法傳遞未命名的引數:

@available(1)
library test.bad.fi0126;

請改為指定引數名稱:

@available(added=1)
library test.good.fi0126;

如果屬性接受多個引數,系統就無法得知您要設定哪個引數,因此會發生這項錯誤。

fi-0127:缺少必要屬性引數

使用具有必要引數的官方屬性時,您無法省略該引數:

library test.bad.fi0127;

@has_required_arg
type Foo = struct {};

請改為提供必要引數:

library test.good.fi0127;

@has_required_arg(required="something")
type Foo = struct {};

fi-0128:缺少單一屬性引數

使用需要單一引數的官方屬性時,不得省略引數:

library test.bad.fi0128;

@transport
protocol Protocol {};

請改為提供引數:

library test.good.fi0128;

@transport("Driver")
protocol Protocol {};

fi-0129:不明屬性引數

使用官方屬性時,您無法提供不在其結構定義中的引數:

@available(added=1, discontinued=2)
library test.bad.fi0129;

如果您想傳遞其他引數,但名稱有誤,請改用正確名稱:

@available(added=1, deprecated=2)
library test.good.fi0129a;

或者,您也可以移除引數:

@available(added=1)
library test.good.fi0129b;

發生這項錯誤的原因是官方屬性會根據結構定義進行驗證。如果 FIDL 允許任意引數,這些引數不會有任何作用,而且可能會因遮蓋錯字而導致錯誤。

fi-0130:重複的屬性引數

屬性不得有兩個名稱相同的引數:

library test.bad.fi0130;

@custom_attribute(custom_arg=true, custom_arg=true)
type Foo = struct {};

請改為只提供一個具有該名稱的引數:

library test.good.fi0130;

@custom_attribute(custom_arg=true)
type Foo = struct {};

fi-0131:重複的標準屬性引數

屬性不能有兩個標準名稱相同的引數:

library test.bad.fi0131;

@custom_attribute(custom_arg=true, CustomArg=true)
type Foo = struct {};

雖然 custom_arg 和 CustomArg 看起來不同,但兩者都以標準名稱 custom_arg 表示。將原始名稱轉換為 snake_case,即可取得正規名稱。

如要修正錯誤,請為每個引數指定正規化後不重複的名稱:

library test.good.fi0131a;

@custom_attribute(custom_arg=true, AnotherCustomArg=true)
type Foo = struct {};

或者,移除其中一個引數:

library test.good.fi0131b;

@custom_attribute(custom_arg=true)
type Foo = struct {};

如要進一步瞭解 FIDL 為何要求宣告必須具有不重複的標準名稱,請參閱 fi-0035。

fi-0132:非預期的屬性引數

使用不接受引數的官方屬性時,您無法提供引數:

library test.bad.fi0132;

type Foo = flexible enum : uint8 {
    @unknown("hello")
    BAR = 1;
};

請改為移除引數:

library test.good.fi0132;

type Foo = flexible enum : uint8 {
    @unknown
    BAR = 1;
};

fi-0133:屬性引數必須是常值

某些官方屬性不允許參照常數的引數:

library test.bad.fi0133;

const NAME string = "MyTable";

type Foo = struct {
    bar @generated_name(NAME) table {};
};

請改為傳遞常值做為引數:

library test.good.fi0133;

type Foo = struct {
    bar @generated_name("MyTable") table {};
};

這些屬性需要常值引數,因為其值會影響編譯。支援非文字引數會難以實作,或在某些情況下因導致矛盾而無法實作。

fi-0134

fi-0135:可供偵測的名稱無效

如果 @discoverable 屬性名稱不正確,就會發生這項錯誤。@discoverable 屬性應為程式庫名稱,後接 . 和通訊協定名稱。

library test.bad.fi0135;

@discoverable(name="test.bad.fi0135/Parser")
protocol Parser {
    Tokenize() -> (struct {
        tokens vector<string>;
    });
};

如要修正這項錯誤,請使用有效的可偵測名稱:

library test.good.fi0135;

@discoverable(name="test.good.fi0135.Parser")
protocol Parser {
    Tokenize() -> (struct {
        tokens vector<string>;
    });
};

fi-0136

fi-0137

fi-0138

fi-0139

fi-0140

fi-0141:錯誤類型無效

方法回應酬載的 error 類型必須是 int32、uint32 或 enum:

library test.bad.fi0141;

protocol MyProtocol {
    MyMethod() -> () error float32;
};

將 error 類型變更為其中一個有效選項,即可修正這項錯誤:

library test.good.fi0141;

protocol MyProtocol {
    MyMethod() -> () error int32;
};

詳情請參閱 RFC-0060:錯誤處理。

fi-0142:通訊協定傳輸類型無效

protocol 宣告上的 @transport(...) 屬性不得指定無效的傳輸:

library test.bad.fi0142;

@transport("Invalid")
protocol MyProtocol {
    MyMethod();
};

請改用下列其中一種支援的傳輸方式:

library test.good.fi0142;

@transport("Syscall")
protocol MyProtocol {
    MyMethod();
};

支援的傳輸方式仍在制定中。如需最新資訊,請參閱 FIDL 屬性。

fi-0143

fi-0144

fi-0145:屬性拼寫錯誤

如果屬性名稱的拼寫與 FIDL 的官方屬性過於相似,會導致編譯器警告:

library test.bad.fi0145;

@duc("should be doc")
protocol Example {
    Method();
};

在上述範例中,屬性 @duc 的拼寫與官方 FIDL 屬性 @doc 太過相似。在這種情況下,屬性命名是刻意為之,而非不小心拼錯官方 FIDL 屬性,因此應修改為足夠獨特的名稱:

library test.good.fi0145;

@duck("quack")
protocol Example {
    Method();
};

除了拼字檢查外,這項警告的目的在於避免使用與官方 FIDL 屬性過於相似的名稱。

拼寫錯誤偵測演算法的運作方式,是計算屬性名稱與每個官方 FIDL 屬性的編輯距離。如果名稱過於相似 (定義為編輯距離太小),就會觸發錯字偵測工具。

fi-0146:產生的名稱無效

如果使用 @generated_name 屬性時名稱無效,就會發生這個錯誤。產生的名稱必須遵循與所有 FIDL ID 相同的規則。

library test.bad.fi0146;

type Device = table {
    1: kind flexible enum {
        DESKTOP = 1;
        PHONE = 2;
    };
};

type Input = table {
    1: kind @generated_name("_kind") flexible enum {
        KEYBOARD = 1;
        MOUSE = 2;
    };
};

如要修正這個問題,請將 @generated_name 值變更為有效的 ID。

library test.good.fi0146;

type Device = table {
    1: kind flexible enum {
        DESKTOP = 1;
        PHONE = 2;
    };
};

type Input = table {
    1: kind @generated_name("input_kind") flexible enum {
        KEYBOARD = 1;
        MOUSE = 2;
    };
};

fi-0147:缺少 @available 引數

如果使用 @available 屬性,但未提供必要引數,就會發生這項錯誤。@available 至少需要 added、deprecated 或 removed 其中一項。

@available(added=1)
library test.bad.fi0147;

@available
type Foo = struct {};

如要修正這個問題,請新增其中一個必要引數:

@available(added=1)
library test.good.fi0147;

@available(added=2)
type Foo = struct {};

詳情請參閱「FIDL 版本管理」。

fi-0148:附註未標示為已淘汰

如果您在 @available 屬性中使用 note 引數,但沒有 deprecated 引數,就會發生這項錯誤。note 僅支援淘汰。

@available(added=1, note="My note")
library test.bad.fi0148;

如要修正這項錯誤,請移除附註:

@available(added=1)
library test.good.fi0148a;

或新增必要的 deprecated 聲明:

@available(added=1, deprecated=2, note="Removed in 2; use X instead.")
library test.good.fi0148b;

詳情請參閱「FIDL 版本管理」。

fi-0149:平台不在程式庫中

如果您嘗試在程式庫宣告以外的位置使用 @available 屬性的 platform 引數,就會發生這項錯誤。platform 引數僅在 library 層級有效。

@available(added=1)
library test.bad.fi0149;

@available(platform="foo")
type Person = struct {
    name string;
};

如要修正這個問題,請將 platform 引數移至程式庫 @available 屬性:

@available(added=1, platform="foo")
library test.good.fi0149a;

type Person = struct {
    name string;
};

或完全移除 platform 引數:

@available(added=1)
library test.good.fi0149b;

type Person = struct {
    name string;
};

fi-0150:缺少圖書館藏書資訊

如果將 @available 屬性新增至程式庫,但未提供 added 引數,就會發生這項錯誤。library @available 屬性必須提供 added 引數。

@available(removed=2)
library test.bad.fi0150a;
@available(platform="foo")
library test.bad.fi0150b;

如要修正這個問題,請將 added 引數新增至程式庫的 @available 屬性:

@available(added=1, removed=2)
library test.good.fi0150a;
@available(added=1, platform="foo")
library test.good.fi0150b;

fi-0151:缺少圖書館供應情形

如果您在非 library 宣告中新增 @available 屬性,但 library 宣告中沒有 @available 屬性,就會發生這項錯誤。

library test.bad.fi0151;

@available(added=1)
type Person = struct {
    name string;
};

如要修正這項錯誤,請在 library 宣告中加入 @available 屬性:

@available(added=1)
library test.good.fi0151a;

@available(added=1)
type Person = struct {
    name string;
};

或從非 library 宣告中移除 @available 屬性:

library test.good.fi0151b;

type Person = struct {
    name string;
};

fi-0152:平台無效

如果 @available 屬性的 platform 引數使用無效字元,就會發生這項錯誤。platform 引數必須是有效的 FIDL 程式庫 ID。

@available(added=1, platform="Spaces are not allowed")
library test.bad.fi0152;

如要修正這項錯誤,請移除不允許的字元:

@available(added=1, platform="foo")
library test.good.fi0152;

fi-0153:版本無效

如果 @available 屬性的 added 或 removed 引數版本無效,就會發生這項錯誤。added 和 removed 引數必須是介於 1 和 2^63-1 之間的正整數,或特殊常數 HEAD。

@available(added=0)
library test.bad.fi0153;

如要修正這個問題,請將版本變更為有效值:

@available(added=1)
library test.good.fi0153;

fi-0154:供應情形順序無效

如果對 @available 屬性使用不當的 added、deprecated 和 remove 引數組合,就會發生這項錯誤。請務必遵守下列限制:

  • added 必須小於或等於 deprecated
  • deprecated 必須小於 removed
  • added 必須小於 removed
@available(added=2, removed=2)
library test.bad.fi0154a;
@available(added=2, deprecated=3, removed=3)
library test.bad.fi0154b;

如要修正這個問題,請將 added、deprecated 和 removed 引數更新為必要順序:

@available(added=1, removed=2)
library test.good.fi0154a;
@available(added=2, deprecated=2, removed=3)
library test.good.fi0154b;

fi-0155:供應情形與上層項目衝突

如果將 @availability 屬性新增至與 library 宣告衝突的非 library 宣告,就會發生這項錯誤。

@available(added=2, deprecated=3, removed=4)
library test.bad.fi0155a;

@available(added=1)
type Person = struct {
    name string;
};
@available(added=2, deprecated=3, removed=4)
library test.bad.fi0155b;

@available(added=4)
type Person = struct {
    name string;
};

如要修正這項錯誤,請將 @availability 屬性更新為符合必要限制:

@available(added=2, deprecated=3, removed=4)
library test.good.fi0155;

@available(added=2)
type Person = struct {
    name string;
};

fi-0156:不得為選填

如果嘗試將不可選用的類型標示為選用,就會發生這項錯誤。

library test.bad.fi0156;

type Person = struct {
    name string;
    age int16:optional;
};

如要修正這項錯誤,請移除選用限制:

library test.good.fi0156;

type Person = struct {
    name string;
    age int16;
};

只有在不變更線路形狀的情況下,才能將 FIDL 類型設為選用,並允許使用 optional 限制。詳情請參閱選用性指南或下方可展開的部分。

FIDL 食譜:選用性

某些 FIDL 類型可以設為選用,只要加入 :optional 限制,就不會變更包含訊息的線路形狀。此外,table 版面配置一律為選用,而 struct 版面配置則一律為必填。如要將 struct 設為選用,必須將其包裝在 box<T> 中,進而變更其包含訊息的線路形狀。

基本類型 選用版本 選用功能會改變線路配置嗎?
struct {...} box<struct {...}> 是
table {...} table {...} 否
union {...} union {...}:optional 否
vector<T> vector<T>:optional 否
string string:optional 否
zx.Handle zx.Handle:optional 否
client_end:P client_end:<P, optional> 否
server_end:P server_end:<P, optional> 否

所有其他類型 (bits、enum、array<T, N> 和原始類型) 都無法設為選用。

這份文件。

在這個變化版本中,我們允許鍵/值儲存庫將其他鍵/值儲存庫視為成員。簡而言之,我們會將其轉換為樹狀結構。方法是將 value 的原始定義替換為使用雙成員 union 的定義:一個變體使用與先前相同的 vector<byte> 類型儲存葉節點,另一個變體則以其他巢狀儲存庫的形式儲存分支節點。

推理

這裡可以看到可選性的幾種用法,我們可以宣告可能存在或不存在的類型。FIDL 有三種選擇性:

  • 這類類型一律會離線儲存在網路上,因此內建一種透過空值封包描述「不存在」的方式。為這些類型啟用選用性,不會影響訊息的傳輸格式,只會變更該特定類型的有效值。union、vector<T>、client_end、server_end 和 zx.Handle 類型都可以透過新增 :optional 限制設為選用。將 value union 設為選填後,我們就能以缺少 value 的形式,引入標準的「null」項目。也就是說,空白的 bytes 和缺少/空白的 store 屬性都是無效值。
  • 與上述類型不同,struct 版面配置沒有額外空間可儲存空值標頭。因此,必須將其包裝在信封中,變更其包含的訊息在傳輸中的形狀。為確保這項線路修改效果清晰易懂,Itemstruct 類型必須包裝在 box<T> 類型範本中。
  • 最後,table 版面配置一律為選用。缺席的 table 只是沒有設定任何成員。

樹狀結構是一種自然自我參照的資料結構:樹狀結構中的任何節點都可能包含具有純資料的葉節點 (在本例中為字串),或具有更多節點的子樹狀結構。這需要遞迴:Item 的定義現在會遞移依附於自身!在 FIDL 中表示遞迴類型可能有點棘手,尤其是因為目前支援有些限制。只要自我參照建立的循環中至少有一個選用類型,我們就能支援這類類型。舉例來說,我們在這裡將 items struct 成員定義為 box<Item>,藉此中斷 include 循環。

這些變更也大量使用「匿名類型」,或是在其唯一使用點內嵌宣告的類型,而不是其本身的命名頂層 type 宣告。根據預設,產生的語言繫結中,匿名類型的名稱會取自其本機內容。舉例來說,新推出的 flexible union 會採用其擁有成員的名稱 Value,新推出的 struct 會變成 Store,以此類推。由於這項經驗法則有時會導致衝突,FIDL 提供了一個應急機制,允許作者手動覆寫匿名類型的生成名稱。方法是透過 @generated_name 屬性,變更後端產生的名稱。我們可以在這裡使用一個,其中可能的 Store 類型會重新命名為 NestedStore,以避免與使用相同名稱的 protocol 宣告發生名稱衝突。

實作

FIDL、CML 和領域介面定義的修改方式如下:

FIDL

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library examples.keyvaluestore.supporttrees;

/// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That
/// is, it must start with a letter, end with a letter or number, contain only letters, numbers,
/// periods, and slashes, and be between 4 and 64 characters long.
type Item = struct {
    key string:128;
    value strict union {
        // Keep the original `bytes` as one of the options in the new union. All leaf nodes in the
        // tree must be `bytes`, or absent unions (representing empty). Empty byte arrays are
        // disallowed.
        1: bytes vector<byte>:64000;

        // Allows a store within a store, thereby turning our flat key-value store into a tree
        // thereof. Note the use of `@generated_name` to prevent a type-name collision with the
        // `Store` protocol below, and the use of `box<T>` to ensure that there is a break in the
        // chain of recursion, thereby allowing `Item` to include itself in its own definition.
        //
        // This is a table so that added fields, like for example a `hash`, can be easily added in
        // the future.
        2: store @generated_name("nested_store") table {
            1: items vector<box<Item>>;
        };
    }:optional;
};

/// An enumeration of things that may go wrong when trying to write a value to our store.
type WriteError = flexible enum {
    UNKNOWN = 0;
    INVALID_KEY = 1;
    INVALID_VALUE = 2;
    ALREADY_EXISTS = 3;
};

/// A very basic key-value store.
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;
};

CML

用戶端

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/client_bin",
    },
    use: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
        {
            config: "fuchsia.examples.keyvaluestore.supporttrees.WriteItems",
            key: "write_items",
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
            availability: "transitional",
            default: [],
        },

        // A newline separated list nested entries. The first line should be the key
        // for the nested store, and each subsequent entry should be a pointer to a text file
        // containing the string value. The name of that text file (without the `.txt` suffix) will
        // serve as the entries key.
        {
            config: "fuchsia.examples.keyvaluestore.supporttrees.WriteNested",
            key: "write_nested",
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
            availability: "transitional",
            default: [],
        },

        // A list of keys, all of which will be populated as null entries.
        {
            config: "fuchsia.examples.keyvaluestore.supporttrees.WriteNull",
            key: "write_null",
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
            availability: "transitional",
            default: [],
        },

    ],
}

伺服器

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/server_bin",
    },
    capabilities: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "self",
        },
    ],
}

Realm

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    children: [
        {
            name: "client",
            url: "#meta/client.cm",
        },
        {
            name: "server",
            url: "#meta/server.cm",
        },
    ],
    offer: [
        // Route the protocol under test from the server to the client.
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "#server",
            to: "#client",
        },

        // Route diagnostics support to all children.
        {
            dictionary: "diagnostics",
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

接著,您可以使用任何支援的語言編寫用戶端和伺服器實作:

荒漠油廠

用戶端

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    config::Config,
    fidl_examples_keyvaluestore_supporttrees::{Item, NestedStore, StoreMarker, Value},
    fuchsia_component::client::connect_to_protocol,
    std::{thread, time},
};

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Load the structured config values passed to this component at startup.
    let config = Config::take_from_startup_handle();

    // Use the Component Framework runtime to connect to the newly spun up server component. We wrap
    // our retained client end in a proxy object that lets us asynchronously send `Store` requests
    // across the channel.
    let store = connect_to_protocol::<StoreMarker>()?;
    println!("Outgoing connection enabled");

    // This client's structured config has one parameter, a vector of strings. Each string is the
    // path to a resource file whose filename is a key and whose contents are a value. We iterate
    // over them and try to write each key-value pair to the remote store.
    for key in config.write_items.into_iter() {
        let path = format!("/pkg/data/{}.txt", key);
        let value = std::fs::read_to_string(path.clone())
            .with_context(|| format!("Failed to load {path}"))?;
        let res = store
            .write_item(&Item {
                key: key.clone(),
                value: Some(Box::new(Value::Bytes(value.into_bytes()))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Add nested entries to the key-value store as well. The entries are strings, where the first
    // line is the key of the entry, and each subsequent entry should be a pointer to a text file
    // containing the string value. The name of that text file (without the `.txt` suffix) will
    // serve as the entries key.
    for spec in config.write_nested.into_iter() {
        let mut items = vec![];
        let mut nested_store = NestedStore::default();
        let mut lines = spec.split("\n");
        let key = lines.next().unwrap();

        // For each entry, make a new entry in the `NestedStore` being built.
        for entry in lines {
            let path = format!("/pkg/data/{}.txt", entry);
            let contents = std::fs::read_to_string(path.clone())
                .with_context(|| format!("Failed to load {path}"))?;
            items.push(Some(Box::new(Item {
                key: entry.to_string(),
                value: Some(Box::new(Value::Bytes(contents.into()))),
            })));
        }
        nested_store.items = Some(items);

        // Send the `NestedStore`, represented as a vector of values.
        let res = store
            .write_item(&Item {
                key: key.to_string(),
                value: Some(Box::new(Value::Store(nested_store))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Each entry in this list is a null value in the store.
    for key in config.write_null.into_iter() {
        match store.write_item(&Item { key: key.to_string(), value: None }).await? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
    // referenced bug has been resolved, we can remove the sleep.
    thread::sleep(time::Duration::from_secs(2));
    Ok(())
}

伺服器

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Note: For the clarity of this example, allow code to be unused.
#![allow(dead_code)]

use {
    anyhow::{Context as _, Error},
    fidl_examples_keyvaluestore_supporttrees::{
        Item, StoreRequest, StoreRequestStream, Value, WriteError,
    },
    fuchsia_component::server::ServiceFs,
    futures::prelude::*,
    regex::Regex,
    std::cell::RefCell,
    std::collections::HashMap,
    std::collections::hash_map::Entry,
    std::str::from_utf8,
    std::sync::LazyLock,
};

static KEY_VALIDATION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r"^[A-Za-z]\w+[A-Za-z0-9]$").expect("Key validation regex failed to compile")
});

// A representation of a key-value store that can contain an arbitrarily deep nesting of other
// key-value stores.
#[allow(clippy::box_collection, reason = "mass allow for https://fxbug.dev/381896734")]
enum StoreNode {
    Leaf(Option<Vec<u8>>),
    Branch(Box<HashMap<String, StoreNode>>),
}

/// Recursive item writer, which takes a `StoreNode` that may not necessarily be the root node, and
/// writes an entry to it.
fn write_item(
    store: &mut HashMap<String, StoreNode>,
    attempt: Item,
    path: &str,
) -> Result<(), WriteError> {
    // Validate the key.
    if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) {
        println!("Write error: INVALID_KEY, For key: {}", attempt.key);
        return Err(WriteError::InvalidKey);
    }

    // Write to the store, validating that the key did not already exist.
    match store.entry(attempt.key) {
        Entry::Occupied(entry) => {
            println!("Write error: ALREADY_EXISTS, For key: {}", entry.key());
            Err(WriteError::AlreadyExists)
        }
        Entry::Vacant(entry) => {
            let key = format!("{}{}", path, entry.key());
            match attempt.value {
                // Null entries are allowed.
                None => {
                    println!("Wrote value: NONE at key: {}", key);
                    entry.insert(StoreNode::Leaf(None));
                }
                Some(value) => match *value {
                    // If this is a nested store, recursively make a new store to insert at this
                    // position.
                    Value::Store(entry_list) => {
                        // Validate the value - absent stores, items lists with no children, or any
                        // of the elements within that list being empty boxes, are all not allowed.
                        if entry_list.items.is_some() {
                            let items = entry_list.items.unwrap();
                            if !items.is_empty() && items.iter().all(|i| i.is_some()) {
                                let nested_path = format!("{}/", key);
                                let mut nested_store = HashMap::<String, StoreNode>::new();
                                for item in items.into_iter() {
                                    write_item(&mut nested_store, *item.unwrap(), &nested_path)?;
                                }

                                println!("Created branch at key: {}", key);
                                entry.insert(StoreNode::Branch(Box::new(nested_store)));
                                return Ok(());
                            }
                        }

                        println!("Write error: INVALID_VALUE, For key: {}", key);
                        return Err(WriteError::InvalidValue);
                    }

                    // This is a simple leaf node on this branch.
                    Value::Bytes(value) => {
                        // Validate the value.
                        if value.is_empty() {
                            println!("Write error: INVALID_VALUE, For key: {}", key);
                            return Err(WriteError::InvalidValue);
                        }

                        println!("Wrote key: {}, value: {:?}", key, from_utf8(&value).unwrap());
                        entry.insert(StoreNode::Leaf(Some(value)));
                    }
                },
            }
            Ok(())
        }
    }
}

/// Creates a new instance of the server. Each server has its own bespoke, per-connection instance
/// of the key-value store.
async fn run_server(stream: StoreRequestStream) -> Result<(), Error> {
    // Create a new in-memory key-value store. The store will live for the lifetime of the
    // connection between the server and this particular client.
    let store = RefCell::new(HashMap::<String, StoreNode>::new());

    // Serve all requests on the protocol sequentially - a new request is not handled until its
    // predecessor has been processed.
    stream
        .map(|result| result.context("failed request"))
        .try_for_each(|request| async {
            // Match based on the method being invoked.
            match request {
                StoreRequest::WriteItem { attempt, responder } => {
                    println!("WriteItem request received");

                    // The `responder` parameter is a special struct that manages the outgoing reply
                    // to this method call. Calling `send` on the responder exactly once will send
                    // the reply.
                    responder
                        .send(write_item(&mut store.borrow_mut(), attempt, ""))
                        .context("error sending reply")?;
                    println!("WriteItem response sent");
                }
                StoreRequest::_UnknownMethod { ordinal, .. } => {
                    println!("Received an unknown method with ordinal {ordinal}");
                }
            }
            Ok(())
        })
        .await
}

// A helper enum that allows us to treat a `Store` service instance as a value.
enum IncomingService {
    Store(StoreRequestStream),
}

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Add a discoverable instance of our `Store` protocol - this will allow the client to see the
    // server and connect to it.
    let mut fs = ServiceFs::new_local();
    fs.dir("svc").add_fidl_service(IncomingService::Store);
    fs.take_and_serve_directory_handle()?;
    println!("Listening for incoming connections");

    // The maximum number of concurrent clients that may be served by this process.
    const MAX_CONCURRENT: usize = 10;

    // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit.
    fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| {
        run_server(stream).unwrap_or_else(|e| println!("{:?}", e))
    })
    .await;

    Ok(())
}

C++ (自然)

用戶端

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

伺服器

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

C++ (Wire)

用戶端

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

伺服器

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

fi-0157:用戶端/伺服器端限制必須是通訊協定

套用至 client_end 或 server_end 的第一個限制條件必須指向 protocol 定義:

library test.bad.fi0157;

type MyStruct = struct {};
alias ServerEnd = server_end:MyStruct;

將限制改為指向通訊協定:

library test.good.fi0157;

protocol MyProtocol {};
alias ServerEnd = server_end:MyProtocol;

fi-0158: Cannot bound twice

alias 宣告無法變更別名類型上已設定的限制值:

library test.bad.fi0158;

alias ByteVec256 = vector<uint8>:256;
alias ByteVec512 = ByteVec256:512;

相反地,無限制定義應接收自己的 alias 宣告,而每個進一步受限的別名應依序繼承該宣告:

library test.good.fi0158;

alias AliasOfVectorOfString = vector<string>;
alias AliasOfVectorOfStringSmall = AliasOfVectorOfString:8;
alias AliasOfVectorOfStringLarge = AliasOfVectorOfString:16;

為避免混淆和編譯器實作複雜性,系統不允許這麼做。

fi-0159:結構體不得為選用

結構體不能有 optional 限制:

library test.bad.fi0159;

type Date = struct {
    year uint16;
    month uint8;
    day uint8;
};

type Person = struct {
    name string;
    birthday Date:optional;
};

將 T:optional 變更為 box<T> 即可修正問題:

library test.good.fi0159;

type Date = struct {
    year uint16;
    month uint8;
    day uint8;
};

type Person = struct {
    name string;
    birthday box<Date>;
};

只有在不變更線路形狀的情況下,才能將 FIDL 類型設為選用,並允許使用 optional 限制。詳情請參閱選用性指南。

fi-0160:類型不能標示為選用兩次

如果將某個類型設為選用兩次,就會發生這項錯誤。通常,當類型在使用和宣告位置都標示為選用時,就會發生這種情況。

library test.bad.fi0160;

alias MyAlias = vector<string>:optional;

type MyStruct = struct {
    my_member MyAlias:optional;
};

如要修正這個錯誤,請只將類型設為選用一次。

例如,您可以從使用網站中移除 :optional。

library test.good.fi0160a;

alias MyAlias = vector<string>:optional;

type MyStruct = struct {
    my_member MyAlias;
};

您也可以從別名宣告中移除 :optional。

library test.good.fi0160b;

alias MyAlias = vector<string>;

type MyStruct = struct {
    my_member MyAlias:optional;
};

fi-0161:大小不得為零

如果嘗試將陣列大小限制設為 0,就會發生這項錯誤。陣列大小不得為零。

library test.bad.fi0161;

type Person = struct {
    name string;
    nicknames array<string, 0>;
};

如要修正這項錯誤,請將大小限制變更為正整數。

library test.good.fi0161;

type Person = struct {
    name string;
    nicknames array<string, 5>;
};

fi-0162:版面配置參數數量有誤

某些 FIDL 版面配置 (例如 vector 和 array) 會採用參數。這項錯誤表示醒目顯示的類型指定的參數數量不正確:

library test.bad.fi0162a;

type Foo = struct {
    bar array<8>;
};

如果非參數化類型錯誤地附加了參數,也可能出現這個錯誤:

library test.bad.fi0162b;

type Foo = struct {
    bar uint8<8>;
};

修正方式一律是為有問題的版面配置指定正確的參數數量:

library test.good.fi0162;

type Foo = struct {
    bar array<uint8, 8>;
};

FIDL 中唯一的參數化類型是 array<T, N>、box<T> 和 vector<T>。client_end 和 server_end 類型在舊版 FIDL 語法中曾是參數化類型,但現在已非如此,不過這仍是經常發生此錯誤的原因。這兩種類型現在會改為採用通訊協定規格做為 (必要) 限制。

參數一律列在角括號 <...> 內,與限制條件有一些相似之處,限制條件會出現在類型結尾的 :... 字元之後。舉例來說,乍看之下,array<T, N> 將大小指定為參數,而 vector<T>:N 將大小指定為限制,這可能看起來很奇怪。差別在於,參數一律會影響相關類型的線路版面配置形狀,而限制只會改變在編碼/解碼時,該類型可接受的值組合,不會影響線路版面配置。

如要進一步瞭解這兩個概念之間的差異,請參閱「RFC-0050:FIDL 語法改版」。

fi-0163:多個限制定義

如果嘗試使用多個冒號 (:) 定義多個限制定義,就會發生這項錯誤。多個限制定義必須使用角括號語法 type:<constraint1, constraint2, etc>。

library test.bad.fi0163;

type Person = struct {
  name string;
  favorite_color string:30:optional;
};

如要修正這項錯誤,請使用角括號語法來設定限制:

library test.good.fi0163;

type Person = struct {
    name string;
    favorite_color string:<30, optional>;
};

fi-0164:限制過多

如果嘗試為某個類型新增的限制超過支援數量,就會發生這項錯誤。例如,string 最多支援兩個限制。

library test.bad.fi0164;

type Person = struct {
    name string:<0, optional, 20>;
};

如要修正這個問題,請移除多餘的限制:

library test.good.fi0164;

type Person = struct {
    name string:<20, optional>;
};

fi-0165: Expected type

如果 FIDL 預期收到類型,但您使用常數或通訊協定 ID,就會發生這項錯誤。

library test.bad.fi0165;

type Person = struct {
    name string;
    nicknames vector<5>;
};

如要修正這項錯誤,請更新程式碼,改用有效類型:

library test.good.fi0165;

type Person = struct {
    name string;
    nicknames vector<string>:5;
};

通訊協定不屬於 FIDL 類型,也無法用於預期類型的場合。

fi-0166:未預期的限制

如果嘗試在不適當的位置使用限制,就會發生這項錯誤。這通常是因為名為 const 的項目位置不正確。

library test.bad.fi0166;

const MIN_SIZE uint8 = 1;
const MAX_SIZE uint8 = 5;

type Person = struct {
    name string;
    nicknames vector<string>:<MIN_SIZE, MAX_SIZE>;
};

如要修正這項錯誤,請移除限制:

library test.good.fi0166;

const MAX_SIZE uint8 = 5;

type Person = struct {
    name string;
    nicknames vector<string>:<MAX_SIZE>;
};

fi-0167: Cannot constrain twice

禁止重新指派已透過 alias 宣告定義傳輸繫結的 client_end 或 server_end 傳輸繫結:

library test.bad.fi0167;

protocol MyOtherProtocol {};

alias ClientEnd = client_end:MyProtocol;
alias ServerEnd = server_end:MyProtocol;

protocol MyProtocol {
    MyMethod(resource struct {
        my_client ClientEnd:MyOtherProtocol;
    }) -> (resource struct {
        my_server ServerEnd:MyOtherProtocol;
    });
};

相反地,應完全避免 client_end 和 server_end 類型的別名:

library test.good.fi0167;

protocol MyProtocol {
    MyMethod(resource struct {
        my_client client_end:MyProtocol;
    }) -> (resource struct {
        my_server server_end:MyProtocol;
    });
};

為避免混淆和編譯器實作複雜性,系統不允許這麼做。

fi-0168:用戶端/伺服器端必須有通訊協定限制

套用至 client_end 或 server_end 的第一個限制條件必須指向 protocol 定義:

library test.bad.fi0168;

protocol MyProtocol {
    MyMethod(resource struct {
        server server_end;
    });
};

新增指向所需通訊協定的限制:

library test.good.fi0168;

protocol MyProtocol {
    MyMethod(resource struct {
        server server_end:MyProtocol;
    });
};

fi-0169: Boxed type cannot be optional

box<T> 形式的類型無法套用 optional 限制:

library test.bad.fi0169;

type Color = struct {
    red byte;
    green byte;
    blue byte;
};

type MyStruct = struct {
    maybe_color box<Color>:optional;
};

根據定義,Boxed 類型為選用,因此不需新增額外限制,否則會造成冗餘:

library test.good.fi0169;

type Color = struct {
    red byte;
    green byte;
    blue byte;
};

type MyStruct = struct {
    maybe_color box<Color>;
};

fi-0170

fi-0171:Boxed 類型應改用選用限制

只有使用 struct 版面配置的類型才能裝箱;union、vector、string、client_end、server_end 和 zx.Handle 必須改用 optional 限制:

library test.bad.fi0171;

using zx;

type MyStruct = resource struct {
    my_resource_member box<zx.Handle>;
};

將 box<T> 轉換為 T:optional 即可修正這個問題:

library test.good.fi0171;

using zx;

type MyStruct = resource struct {
    my_resource_member zx.Handle:optional;
};

只有在不變更線路形狀的情況下,才能將 FIDL 類型設為選用,並允許使用 optional 限制。詳情請參閱選用性指南或下方可展開的部分。

FIDL 食譜:選用性

某些 FIDL 類型可以設為選用,只要加入 :optional 限制,就不會變更包含訊息的線路形狀。此外,table 版面配置一律為選用,而 struct 版面配置則一律為必填。如要將 struct 設為選用,必須將其包裝在 box<T> 中,進而變更其包含訊息的線路形狀。

基本類型 選用版本 選用功能會改變線路配置嗎?
struct {...} box<struct {...}> 是
table {...} table {...} 否
union {...} union {...}:optional 否
vector<T> vector<T>:optional 否
string string:optional 否
zx.Handle zx.Handle:optional 否
client_end:P client_end:<P, optional> 否
server_end:P server_end:<P, optional> 否

所有其他類型 (bits、enum、array<T, N> 和原始類型) 都無法設為選用。

這份文件。

在這個變化版本中,我們允許鍵/值儲存庫將其他鍵/值儲存庫視為成員。簡而言之,我們會將其轉換為樹狀結構。方法是將 value 的原始定義替換為使用雙成員 union 的定義:一個變體使用與先前相同的 vector<byte> 類型儲存葉節點,另一個變體則以其他巢狀儲存庫的形式儲存分支節點。

推理

這裡可以看到可選性的幾種用法,我們可以宣告可能存在或不存在的類型。FIDL 有三種選擇性:

  • 這類類型一律會離線儲存在網路上,因此內建一種透過空值封包描述「不存在」的方式。為這些類型啟用選用性,不會影響訊息的傳輸格式,只會變更該特定類型的有效值。union、vector<T>、client_end、server_end 和 zx.Handle 類型都可以透過新增 :optional 限制設為選用。將 value union 設為選填後,我們就能以缺少 value 的形式,引入標準的「null」項目。也就是說,空白的 bytes 和缺少/空白的 store 屬性都是無效值。
  • 與上述類型不同,struct 版面配置沒有額外空間可儲存空值標頭。因此,必須將其包裝在信封中,變更其包含的訊息在傳輸中的形狀。為確保這項線路修改效果清晰易懂,Itemstruct 類型必須包裝在 box<T> 類型範本中。
  • 最後,table 版面配置一律為選用。缺席的 table 只是沒有設定任何成員。

樹狀結構是一種自然自我參照的資料結構:樹狀結構中的任何節點都可能包含具有純資料的葉節點 (在本例中為字串),或具有更多節點的子樹狀結構。這需要遞迴:Item 的定義現在會遞移依附於自身!在 FIDL 中表示遞迴類型可能有點棘手,尤其是因為目前支援有些限制。只要自我參照建立的循環中至少有一個選用類型,我們就能支援這類類型。舉例來說,我們在這裡將 items struct 成員定義為 box<Item>,藉此中斷 include 循環。

這些變更也大量使用「匿名類型」,或是在其唯一使用點內嵌宣告的類型,而不是其本身的命名頂層 type 宣告。根據預設,產生的語言繫結中,匿名類型的名稱會取自其本機內容。舉例來說,新推出的 flexible union 會採用其擁有成員的名稱 Value,新推出的 struct 會變成 Store,以此類推。由於這項經驗法則有時會導致衝突,FIDL 提供了一個應急機制,允許作者手動覆寫匿名類型的生成名稱。方法是透過 @generated_name 屬性,變更後端產生的名稱。我們可以在這裡使用一個,其中可能的 Store 類型會重新命名為 NestedStore,以避免與使用相同名稱的 protocol 宣告發生名稱衝突。

實作

FIDL、CML 和領域介面定義的修改方式如下:

FIDL

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library examples.keyvaluestore.supporttrees;

/// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That
/// is, it must start with a letter, end with a letter or number, contain only letters, numbers,
/// periods, and slashes, and be between 4 and 64 characters long.
type Item = struct {
    key string:128;
    value strict union {
        // Keep the original `bytes` as one of the options in the new union. All leaf nodes in the
        // tree must be `bytes`, or absent unions (representing empty). Empty byte arrays are
        // disallowed.
        1: bytes vector<byte>:64000;

        // Allows a store within a store, thereby turning our flat key-value store into a tree
        // thereof. Note the use of `@generated_name` to prevent a type-name collision with the
        // `Store` protocol below, and the use of `box<T>` to ensure that there is a break in the
        // chain of recursion, thereby allowing `Item` to include itself in its own definition.
        //
        // This is a table so that added fields, like for example a `hash`, can be easily added in
        // the future.
        2: store @generated_name("nested_store") table {
            1: items vector<box<Item>>;
        };
    }:optional;
};

/// An enumeration of things that may go wrong when trying to write a value to our store.
type WriteError = flexible enum {
    UNKNOWN = 0;
    INVALID_KEY = 1;
    INVALID_VALUE = 2;
    ALREADY_EXISTS = 3;
};

/// A very basic key-value store.
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;
};

CML

用戶端

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/client_bin",
    },
    use: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
        {
            config: "fuchsia.examples.keyvaluestore.supporttrees.WriteItems",
            key: "write_items",
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
            availability: "transitional",
            default: [],
        },

        // A newline separated list nested entries. The first line should be the key
        // for the nested store, and each subsequent entry should be a pointer to a text file
        // containing the string value. The name of that text file (without the `.txt` suffix) will
        // serve as the entries key.
        {
            config: "fuchsia.examples.keyvaluestore.supporttrees.WriteNested",
            key: "write_nested",
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
            availability: "transitional",
            default: [],
        },

        // A list of keys, all of which will be populated as null entries.
        {
            config: "fuchsia.examples.keyvaluestore.supporttrees.WriteNull",
            key: "write_null",
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
            availability: "transitional",
            default: [],
        },

    ],
}

伺服器

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/server_bin",
    },
    capabilities: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "self",
        },
    ],
}

Realm

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    children: [
        {
            name: "client",
            url: "#meta/client.cm",
        },
        {
            name: "server",
            url: "#meta/server.cm",
        },
    ],
    offer: [
        // Route the protocol under test from the server to the client.
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "#server",
            to: "#client",
        },

        // Route diagnostics support to all children.
        {
            dictionary: "diagnostics",
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

接著,您可以使用任何支援的語言編寫用戶端和伺服器實作:

荒漠油廠

用戶端

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    config::Config,
    fidl_examples_keyvaluestore_supporttrees::{Item, NestedStore, StoreMarker, Value},
    fuchsia_component::client::connect_to_protocol,
    std::{thread, time},
};

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Load the structured config values passed to this component at startup.
    let config = Config::take_from_startup_handle();

    // Use the Component Framework runtime to connect to the newly spun up server component. We wrap
    // our retained client end in a proxy object that lets us asynchronously send `Store` requests
    // across the channel.
    let store = connect_to_protocol::<StoreMarker>()?;
    println!("Outgoing connection enabled");

    // This client's structured config has one parameter, a vector of strings. Each string is the
    // path to a resource file whose filename is a key and whose contents are a value. We iterate
    // over them and try to write each key-value pair to the remote store.
    for key in config.write_items.into_iter() {
        let path = format!("/pkg/data/{}.txt", key);
        let value = std::fs::read_to_string(path.clone())
            .with_context(|| format!("Failed to load {path}"))?;
        let res = store
            .write_item(&Item {
                key: key.clone(),
                value: Some(Box::new(Value::Bytes(value.into_bytes()))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Add nested entries to the key-value store as well. The entries are strings, where the first
    // line is the key of the entry, and each subsequent entry should be a pointer to a text file
    // containing the string value. The name of that text file (without the `.txt` suffix) will
    // serve as the entries key.
    for spec in config.write_nested.into_iter() {
        let mut items = vec![];
        let mut nested_store = NestedStore::default();
        let mut lines = spec.split("\n");
        let key = lines.next().unwrap();

        // For each entry, make a new entry in the `NestedStore` being built.
        for entry in lines {
            let path = format!("/pkg/data/{}.txt", entry);
            let contents = std::fs::read_to_string(path.clone())
                .with_context(|| format!("Failed to load {path}"))?;
            items.push(Some(Box::new(Item {
                key: entry.to_string(),
                value: Some(Box::new(Value::Bytes(contents.into()))),
            })));
        }
        nested_store.items = Some(items);

        // Send the `NestedStore`, represented as a vector of values.
        let res = store
            .write_item(&Item {
                key: key.to_string(),
                value: Some(Box::new(Value::Store(nested_store))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Each entry in this list is a null value in the store.
    for key in config.write_null.into_iter() {
        match store.write_item(&Item { key: key.to_string(), value: None }).await? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
    // referenced bug has been resolved, we can remove the sleep.
    thread::sleep(time::Duration::from_secs(2));
    Ok(())
}

伺服器

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Note: For the clarity of this example, allow code to be unused.
#![allow(dead_code)]

use {
    anyhow::{Context as _, Error},
    fidl_examples_keyvaluestore_supporttrees::{
        Item, StoreRequest, StoreRequestStream, Value, WriteError,
    },
    fuchsia_component::server::ServiceFs,
    futures::prelude::*,
    regex::Regex,
    std::cell::RefCell,
    std::collections::HashMap,
    std::collections::hash_map::Entry,
    std::str::from_utf8,
    std::sync::LazyLock,
};

static KEY_VALIDATION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r"^[A-Za-z]\w+[A-Za-z0-9]$").expect("Key validation regex failed to compile")
});

// A representation of a key-value store that can contain an arbitrarily deep nesting of other
// key-value stores.
#[allow(clippy::box_collection, reason = "mass allow for https://fxbug.dev/381896734")]
enum StoreNode {
    Leaf(Option<Vec<u8>>),
    Branch(Box<HashMap<String, StoreNode>>),
}

/// Recursive item writer, which takes a `StoreNode` that may not necessarily be the root node, and
/// writes an entry to it.
fn write_item(
    store: &mut HashMap<String, StoreNode>,
    attempt: Item,
    path: &str,
) -> Result<(), WriteError> {
    // Validate the key.
    if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) {
        println!("Write error: INVALID_KEY, For key: {}", attempt.key);
        return Err(WriteError::InvalidKey);
    }

    // Write to the store, validating that the key did not already exist.
    match store.entry(attempt.key) {
        Entry::Occupied(entry) => {
            println!("Write error: ALREADY_EXISTS, For key: {}", entry.key());
            Err(WriteError::AlreadyExists)
        }
        Entry::Vacant(entry) => {
            let key = format!("{}{}", path, entry.key());
            match attempt.value {
                // Null entries are allowed.
                None => {
                    println!("Wrote value: NONE at key: {}", key);
                    entry.insert(StoreNode::Leaf(None));
                }
                Some(value) => match *value {
                    // If this is a nested store, recursively make a new store to insert at this
                    // position.
                    Value::Store(entry_list) => {
                        // Validate the value - absent stores, items lists with no children, or any
                        // of the elements within that list being empty boxes, are all not allowed.
                        if entry_list.items.is_some() {
                            let items = entry_list.items.unwrap();
                            if !items.is_empty() && items.iter().all(|i| i.is_some()) {
                                let nested_path = format!("{}/", key);
                                let mut nested_store = HashMap::<String, StoreNode>::new();
                                for item in items.into_iter() {
                                    write_item(&mut nested_store, *item.unwrap(), &nested_path)?;
                                }

                                println!("Created branch at key: {}", key);
                                entry.insert(StoreNode::Branch(Box::new(nested_store)));
                                return Ok(());
                            }
                        }

                        println!("Write error: INVALID_VALUE, For key: {}", key);
                        return Err(WriteError::InvalidValue);
                    }

                    // This is a simple leaf node on this branch.
                    Value::Bytes(value) => {
                        // Validate the value.
                        if value.is_empty() {
                            println!("Write error: INVALID_VALUE, For key: {}", key);
                            return Err(WriteError::InvalidValue);
                        }

                        println!("Wrote key: {}, value: {:?}", key, from_utf8(&value).unwrap());
                        entry.insert(StoreNode::Leaf(Some(value)));
                    }
                },
            }
            Ok(())
        }
    }
}

/// Creates a new instance of the server. Each server has its own bespoke, per-connection instance
/// of the key-value store.
async fn run_server(stream: StoreRequestStream) -> Result<(), Error> {
    // Create a new in-memory key-value store. The store will live for the lifetime of the
    // connection between the server and this particular client.
    let store = RefCell::new(HashMap::<String, StoreNode>::new());

    // Serve all requests on the protocol sequentially - a new request is not handled until its
    // predecessor has been processed.
    stream
        .map(|result| result.context("failed request"))
        .try_for_each(|request| async {
            // Match based on the method being invoked.
            match request {
                StoreRequest::WriteItem { attempt, responder } => {
                    println!("WriteItem request received");

                    // The `responder` parameter is a special struct that manages the outgoing reply
                    // to this method call. Calling `send` on the responder exactly once will send
                    // the reply.
                    responder
                        .send(write_item(&mut store.borrow_mut(), attempt, ""))
                        .context("error sending reply")?;
                    println!("WriteItem response sent");
                }
                StoreRequest::_UnknownMethod { ordinal, .. } => {
                    println!("Received an unknown method with ordinal {ordinal}");
                }
            }
            Ok(())
        })
        .await
}

// A helper enum that allows us to treat a `Store` service instance as a value.
enum IncomingService {
    Store(StoreRequestStream),
}

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Add a discoverable instance of our `Store` protocol - this will allow the client to see the
    // server and connect to it.
    let mut fs = ServiceFs::new_local();
    fs.dir("svc").add_fidl_service(IncomingService::Store);
    fs.take_and_serve_directory_handle()?;
    println!("Listening for incoming connections");

    // The maximum number of concurrent clients that may be served by this process.
    const MAX_CONCURRENT: usize = 10;

    // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit.
    fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| {
        run_server(stream).unwrap_or_else(|e| println!("{:?}", e))
    })
    .await;

    Ok(())
}

C++ (自然)

用戶端

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

伺服器

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

C++ (Wire)

用戶端

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

伺服器

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

fi-0172:資源定義必須使用 uint32 子類型

resource_definition 宣告的子類型必須是 uint32:

library test.bad.fi0172;

type MySubtype = strict enum : uint32 {
    NONE = 0;
};

resource_definition MyResource : uint8 {
    properties {
        subtype MySubtype;
    };
};

將子類型變更為 uint32,即可修正這項錯誤:

library test.good.fi0172;

type MySubtype = strict enum : uint32 {
    NONE = 0;
};

resource_definition MyResource : uint32 {
    properties {
        subtype MySubtype;
    };
};

這項錯誤與 FIDL 的內部實作有關,因此只會向開發 FIDL 核心程式庫的開發人員顯示。使用者不應看到這項錯誤。

它參照的 resource_definition 宣告是 FIDL 定義控點等資源的內部方法,日後可能會隨著控點一般化作業而變更。

fi-0173:資源定義必須指定子類型

resource_definition 宣告不能省略 subtype 成員:

library test.bad.fi0173;

resource_definition MyResource : uint32 {
    properties {
        rights uint32;
    };
};

將這個成員指向有效的 enum : uint32 宣告:

library test.good.fi0173;

resource_definition MyResource : uint32 {
    properties {
        subtype flexible enum : uint32 {};
        rights uint32;
    };
};

這項錯誤與 FIDL 的內部實作有關,因此只會向開發 FIDL 核心程式庫的開發人員顯示。使用者不應看到這項錯誤。

它參照的 resource_definition 宣告是 FIDL 定義控點等資源的內部方法,日後可能會隨著控點一般化作業而變更。

fi-0174

fi-0175:資源定義子類型屬性必須參照列舉

resource_definition 宣告無法使用非 enum 做為 subtype 成員:

library test.bad.fi0175;

resource_definition MyResource : uint32 {
    properties {
        subtype struct {};
    };
};

將這個成員指向有效的 enum : uint32 宣告:

library test.good.fi0175;

resource_definition MyResource : uint32 {
    properties {
        subtype flexible enum : uint32 {};
    };
};

這項錯誤與 FIDL 的內部實作有關,因此只會向開發 FIDL 核心程式庫的開發人員顯示。使用者不應看到這項錯誤。

它參照的 resource_definition 宣告是 FIDL 定義控點等資源的內部方法,日後可能會隨著控點一般化作業而變更。

fi-0176

fi-0177:資源定義權利屬性必須參照位元

resource_definition 宣告無法使用非 bits 做為 rights 成員:

library test.bad.fi0177;

type MySubtype = enum : uint32 {
    NONE = 0;
    VMO = 3;
};

resource_definition MyResource : uint32 {
    properties {
        subtype MySubtype;
        rights string;
    };
};

將這個成員指向有效的 bits : uint32 宣告:

library test.good.fi0177;

type MySubtype = enum : uint32 {
    NONE = 0;
    VMO = 3;
};

resource_definition MyResource : uint32 {
    properties {
        subtype MySubtype;
        rights uint32;
    };
};

這項錯誤與 FIDL 的內部實作有關,因此只會向開發 FIDL 核心程式庫的開發人員顯示。使用者不應看到這項錯誤。

它參照的 resource_definition 宣告是 FIDL 定義控點等資源的內部方法,日後可能會隨著控點一般化作業而變更。

fi-0178:未使用的匯入

未參照透過 using 宣告匯入的依附元件,會導致錯誤:

library test.bad.fi0178;

using dependent;

type Foo = struct {
    does_not int64;
    use_dependent int32;
};

請確認所有這類匯入項目都用於程式庫匯入,方法是實際參照匯入項目,或移除未使用的依附元件:

library test.good.fi0178;

using dependent;

type Foo = struct {
    dep dependent.Bar;
};

fi-0179:無法限制新類型

RFC-0052:類型別名和新類型中的新類型不得受到限制。舉例來說,string 的新類型無法受到 :optional 的限制:

library test.bad.fi0179;

type Name = string;

type Info = struct {
    name Name:optional;
};

在這種情況下,我們可以將 name 欄位放入資料表 (而非結構體),將其設為選用欄位:

library test.good.fi0179;

type Name = string;

type Info = table {
    1: name Name;
};

這項限制可簡化 newtype 的設計。一般來說,目前尚不清楚受限 newtype 的 API 和 ABI 應為何 (例如,限制應套用至 newtype 本身,還是套用至基礎類型?)。

fi-0180:Zircon C 類型為實驗性質

內建類型 usize、uintptr、uchar 和 experimental_pointer 正在為 Zither 專案開發。無法在一般 FIDL 程式庫中使用:

library test.bad.fi0180;

type Data = struct {
    size usize64;
};

請改用其他類型,例如使用 uint64 而非 usize:

library test.good.fi0180;

type Data = struct {
    size uint64;
};

fi-0181:程式庫屬性引數參照常數

程式庫宣告中的屬性引數不得參照常數:

@custom_attribute(VALUE)
library test.bad.fi0181a;

const VALUE string = "hello";

請改為提供常值引數:

@custom_attribute("hello")
library test.good.fi0181a;

這項限制存在的原因是,這種做法很少需要,且支援這種做法會增加編譯器的不必要複雜度。

fi-0182

fi-0183

fi-0184:非預期的控制字元

字串常值不得包含原始控制字元 (0x00 到 0x1f 的 ASCII 字元):

library test.bad.fi0184;

const TAB string = "	"; // literal tab character

請改用逸出序列。在本例中,\t 是正確的:

library test.good.fi0184a;

const TAB string = "\t";

或者,您也可以使用 Unicode 逸出序列。這適用於任何 Unicode 碼點:

library test.good.fi0184b;

const TAB string = "\u{9}";

字串常值中不得使用原始控制字元,因為這些字元都是空白字元或不可列印字元,如果直接嵌入 FIDL 來源檔案,會令人混淆且難以察覺。

fi-0185:萬國碼 (Unicode) 逸出序列缺少大括號

字串常值中的 Unicode 逸出序列必須在大括號中指定碼點:

library test.bad.fi0185;

const SMILE string = "\u";

如要修正錯誤,請在大括號中指定碼點:

library test.good.fi0185;

const SMILE string = "\u{1F600}";

fi-0186:萬國碼 (Unicode) 逸出序列未終止

字串常值中的 Unicode 逸出序列必須終止:

library test.bad.fi0186;

const SMILE string = "\u{1F600";

如要終止逸出序列,請加上右大括號 }:

library test.good.fi0186;

const SMILE string = "\u{1F600}";

fi-0187:空白的萬國碼 (Unicode) 逸出序列

字串常值中的 Unicode 逸出序列必須至少有一個十六進位數字:

library test.bad.fi0187;

const SMILE string = "\u{}";

如要修正錯誤,請加入十六進位數字來指定 Unicode 碼點:

library test.good.fi0187;

const SMILE string = "\u{1F600}";

fi-0188:萬國碼 (Unicode) 逸出序列中的數字過多

字串常值中的 Unicode 逸出序列不得超過 6 個十六進位數字:

library test.bad.fi0188;

const SMILE string = "\u{001F600}";

如要修正錯誤,請指定最多 6 個十六進位數字。在這個例子中,我們可以移除開頭的零:

library test.good.fi0188;

const SMILE string = "\u{1F600}";

這項限制的原因是所有有效的 Unicode 碼點都符合 6 個十六進位數字,因此沒有理由允許更多數字。

fi-0189:萬國碼 (Unicode) 碼點過大

字串常值中的 Unicode 逸出序列,不得指定大於 0x10ffff 上限的 Unicode 碼點:

library test.bad.fi0189;

const TOO_LARGE string = "\u{110000}";

請改為確認碼點有效:

library test.good.fi0189;

const MAX_CODEPOINT string = "\u{10ffff}";

fi-0190

fi-0191:方法必須指定嚴格程度

這項錯誤表示 FIDL 方法沒有 strict 或 flexible 修飾符。

library test.bad.fi0191;

open protocol Example {
    OneWay();
};

如要修正這個問題,請在方法中加入 strict 或 flexible。如果這是現有方法,您必須使用 strict,並參閱相容性指南,瞭解如何將其變更為 flexible。如果是新方法,請參閱 API 評分標準,瞭解如何選擇。

library test.good.fi0191;

open protocol Example {
    flexible OneWay();
};

FIDL 目前正在進行遷移,以支援處理 RFC-0138 中定義的未知互動。這項新功能可將修飾符 strict 和 flexible 套用至 FIDL 方法和事件。過去,所有方法的行為都與 strict 相同,但在這項遷移作業結束後,預設值將為 flexible。為避免混淆,以及因將方法預設修飾符從 strict 變更為 flexible 而可能發生的問題,在這個過渡期間,必須使用方法修飾符。遷移完成後,這項錯誤會變更為 Linter 建議。

如要進一步瞭解不明互動,請參閱 FIDL 語言參考資料。

fi-0192:通訊協定必須指定開放性

這項錯誤表示 FIDL 通訊協定沒有 open、ajar 或 closed 修飾符。

library test.bad.fi0192;

protocol ImplicitOpenness {};

如要修正這個問題,請在通訊協定中加入 open、ajar 或 closed。如果這是現有通訊協定,您必須使用 closed,並參閱相容性指南,瞭解如何將其變更為 open 或 ajar。如果是新方法,請參閱 API 評分標準,瞭解如何選擇。

library test.good.fi0192;

open protocol ImplicitOpenness {};

FIDL 目前正在進行遷移,以支援處理 RFC-0138 中定義的未知互動。這項新功能新增了三個修飾符:open、ajar 和 closed,適用於 FIDL 通訊協定。過去,所有通訊協定的行為都與 closed 相同,但這項遷移作業結束後,預設值將變更為 open。為避免混淆,以及因通訊協定預設修飾符從 closed 變更為 open 而可能發生的問題,在這個過渡期內,必須使用通訊協定修飾符。遷移完成後,這項錯誤會變更為 Linter 建議。

如要進一步瞭解不明互動,請參閱 FIDL 語言參考資料。

fi-0193: Cannot box type

無法對 struct 以外的類型進行裝箱。舉例來說,原始類型無法裝箱:

library test.bad.fi0193;

type MyStruct = struct {
    my_member box<bool>;
};

如要對原始類型進行裝箱,請改為將其放入單一成員 struct:

library test.good.fi0193;

type MyStruct = struct {
    my_member box<struct {
        my_bool bool;
    }>;
};

請注意,部分類型可透過 optional 限制設為選用。詳情請參閱選用性指南,或下方可展開的部分。

FIDL 食譜:選用性

某些 FIDL 類型可以設為選用,只要加入 :optional 限制,就不會變更包含訊息的線路形狀。此外,table 版面配置一律為選用,而 struct 版面配置則一律為必填。如要將 struct 設為選用,必須將其包裝在 box<T> 中,進而變更其包含訊息的線路形狀。

基本類型 選用版本 選用功能會改變線路配置嗎?
struct {...} box<struct {...}> 是
table {...} table {...} 否
union {...} union {...}:optional 否
vector<T> vector<T>:optional 否
string string:optional 否
zx.Handle zx.Handle:optional 否
client_end:P client_end:<P, optional> 否
server_end:P server_end:<P, optional> 否

所有其他類型 (bits、enum、array<T, N> 和原始類型) 都無法設為選用。

這份文件。

在這個變化版本中,我們允許鍵/值儲存庫將其他鍵/值儲存庫視為成員。簡而言之,我們會將其轉換為樹狀結構。方法是將 value 的原始定義替換為使用雙成員 union 的定義:一個變體使用與先前相同的 vector<byte> 類型儲存葉節點,另一個變體則以其他巢狀儲存庫的形式儲存分支節點。

推理

這裡可以看到可選性的幾種用法,我們可以宣告可能存在或不存在的類型。FIDL 有三種選擇性:

  • 這類類型一律會離線儲存在網路上,因此內建一種透過空值封包描述「不存在」的方式。為這些類型啟用選用性,不會影響訊息的傳輸格式,只會變更該特定類型的有效值。union、vector<T>、client_end、server_end 和 zx.Handle 類型都可以透過新增 :optional 限制設為選用。將 value union 設為選填後,我們就能以缺少 value 的形式,引入標準的「null」項目。也就是說,空白的 bytes 和缺少/空白的 store 屬性都是無效值。
  • 與上述類型不同,struct 版面配置沒有額外空間可儲存空值標頭。因此,必須將其包裝在信封中,變更其包含的訊息在傳輸中的形狀。為確保這項線路修改效果清晰易懂,Itemstruct 類型必須包裝在 box<T> 類型範本中。
  • 最後,table 版面配置一律為選用。缺席的 table 只是沒有設定任何成員。

樹狀結構是一種自然自我參照的資料結構:樹狀結構中的任何節點都可能包含具有純資料的葉節點 (在本例中為字串),或具有更多節點的子樹狀結構。這需要遞迴:Item 的定義現在會遞移依附於自身!在 FIDL 中表示遞迴類型可能有點棘手,尤其是因為目前支援有些限制。只要自我參照建立的循環中至少有一個選用類型,我們就能支援這類類型。舉例來說,我們在這裡將 items struct 成員定義為 box<Item>,藉此中斷 include 循環。

這些變更也大量使用「匿名類型」,或是在其唯一使用點內嵌宣告的類型,而不是其本身的命名頂層 type 宣告。根據預設,產生的語言繫結中,匿名類型的名稱會取自其本機內容。舉例來說,新推出的 flexible union 會採用其擁有成員的名稱 Value,新推出的 struct 會變成 Store,以此類推。由於這項經驗法則有時會導致衝突,FIDL 提供了一個應急機制,允許作者手動覆寫匿名類型的生成名稱。方法是透過 @generated_name 屬性,變更後端產生的名稱。我們可以在這裡使用一個,其中可能的 Store 類型會重新命名為 NestedStore,以避免與使用相同名稱的 protocol 宣告發生名稱衝突。

實作

FIDL、CML 和領域介面定義的修改方式如下:

FIDL

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library examples.keyvaluestore.supporttrees;

/// An item in the store. The key must match the regex `^[A-z][A-z0-9_\.\/]{2,62}[A-z0-9]$`. That
/// is, it must start with a letter, end with a letter or number, contain only letters, numbers,
/// periods, and slashes, and be between 4 and 64 characters long.
type Item = struct {
    key string:128;
    value strict union {
        // Keep the original `bytes` as one of the options in the new union. All leaf nodes in the
        // tree must be `bytes`, or absent unions (representing empty). Empty byte arrays are
        // disallowed.
        1: bytes vector<byte>:64000;

        // Allows a store within a store, thereby turning our flat key-value store into a tree
        // thereof. Note the use of `@generated_name` to prevent a type-name collision with the
        // `Store` protocol below, and the use of `box<T>` to ensure that there is a break in the
        // chain of recursion, thereby allowing `Item` to include itself in its own definition.
        //
        // This is a table so that added fields, like for example a `hash`, can be easily added in
        // the future.
        2: store @generated_name("nested_store") table {
            1: items vector<box<Item>>;
        };
    }:optional;
};

/// An enumeration of things that may go wrong when trying to write a value to our store.
type WriteError = flexible enum {
    UNKNOWN = 0;
    INVALID_KEY = 1;
    INVALID_VALUE = 2;
    ALREADY_EXISTS = 3;
};

/// A very basic key-value store.
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;
};

CML

用戶端

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/client_bin",
    },
    use: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
        {
            config: "fuchsia.examples.keyvaluestore.supporttrees.WriteItems",
            key: "write_items",
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
            availability: "transitional",
            default: [],
        },

        // A newline separated list nested entries. The first line should be the key
        // for the nested store, and each subsequent entry should be a pointer to a text file
        // containing the string value. The name of that text file (without the `.txt` suffix) will
        // serve as the entries key.
        {
            config: "fuchsia.examples.keyvaluestore.supporttrees.WriteNested",
            key: "write_nested",
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
            availability: "transitional",
            default: [],
        },

        // A list of keys, all of which will be populated as null entries.
        {
            config: "fuchsia.examples.keyvaluestore.supporttrees.WriteNull",
            key: "write_null",
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
            availability: "transitional",
            default: [],
        },

    ],
}

伺服器

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "syslog/client.shard.cml" ],
    program: {
        runner: "elf",
        binary: "bin/server_bin",
    },
    capabilities: [
        { protocol: "examples.keyvaluestore.supporttrees.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "self",
        },
    ],
}

Realm

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    children: [
        {
            name: "client",
            url: "#meta/client.cm",
        },
        {
            name: "server",
            url: "#meta/server.cm",
        },
    ],
    offer: [
        // Route the protocol under test from the server to the client.
        {
            protocol: "examples.keyvaluestore.supporttrees.Store",
            from: "#server",
            to: "#client",
        },

        // Route diagnostics support to all children.
        {
            dictionary: "diagnostics",
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

接著,您可以使用任何支援的語言編寫用戶端和伺服器實作:

荒漠油廠

用戶端

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use {
    anyhow::{Context as _, Error},
    config::Config,
    fidl_examples_keyvaluestore_supporttrees::{Item, NestedStore, StoreMarker, Value},
    fuchsia_component::client::connect_to_protocol,
    std::{thread, time},
};

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Load the structured config values passed to this component at startup.
    let config = Config::take_from_startup_handle();

    // Use the Component Framework runtime to connect to the newly spun up server component. We wrap
    // our retained client end in a proxy object that lets us asynchronously send `Store` requests
    // across the channel.
    let store = connect_to_protocol::<StoreMarker>()?;
    println!("Outgoing connection enabled");

    // This client's structured config has one parameter, a vector of strings. Each string is the
    // path to a resource file whose filename is a key and whose contents are a value. We iterate
    // over them and try to write each key-value pair to the remote store.
    for key in config.write_items.into_iter() {
        let path = format!("/pkg/data/{}.txt", key);
        let value = std::fs::read_to_string(path.clone())
            .with_context(|| format!("Failed to load {path}"))?;
        let res = store
            .write_item(&Item {
                key: key.clone(),
                value: Some(Box::new(Value::Bytes(value.into_bytes()))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Add nested entries to the key-value store as well. The entries are strings, where the first
    // line is the key of the entry, and each subsequent entry should be a pointer to a text file
    // containing the string value. The name of that text file (without the `.txt` suffix) will
    // serve as the entries key.
    for spec in config.write_nested.into_iter() {
        let mut items = vec![];
        let mut nested_store = NestedStore::default();
        let mut lines = spec.split("\n");
        let key = lines.next().unwrap();

        // For each entry, make a new entry in the `NestedStore` being built.
        for entry in lines {
            let path = format!("/pkg/data/{}.txt", entry);
            let contents = std::fs::read_to_string(path.clone())
                .with_context(|| format!("Failed to load {path}"))?;
            items.push(Some(Box::new(Item {
                key: entry.to_string(),
                value: Some(Box::new(Value::Bytes(contents.into()))),
            })));
        }
        nested_store.items = Some(items);

        // Send the `NestedStore`, represented as a vector of values.
        let res = store
            .write_item(&Item {
                key: key.to_string(),
                value: Some(Box::new(Value::Store(nested_store))),
            })
            .await;
        match res? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // Each entry in this list is a null value in the store.
    for key in config.write_null.into_iter() {
        match store.write_item(&Item { key: key.to_string(), value: None }).await? {
            Ok(_) => println!("WriteItem Success at key: {}", key),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // TODO(https://fxbug.dev/42156498): We need to sleep here to make sure all logs get drained. Once the
    // referenced bug has been resolved, we can remove the sleep.
    thread::sleep(time::Duration::from_secs(2));
    Ok(())
}

伺服器

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Note: For the clarity of this example, allow code to be unused.
#![allow(dead_code)]

use {
    anyhow::{Context as _, Error},
    fidl_examples_keyvaluestore_supporttrees::{
        Item, StoreRequest, StoreRequestStream, Value, WriteError,
    },
    fuchsia_component::server::ServiceFs,
    futures::prelude::*,
    regex::Regex,
    std::cell::RefCell,
    std::collections::HashMap,
    std::collections::hash_map::Entry,
    std::str::from_utf8,
    std::sync::LazyLock,
};

static KEY_VALIDATION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r"^[A-Za-z]\w+[A-Za-z0-9]$").expect("Key validation regex failed to compile")
});

// A representation of a key-value store that can contain an arbitrarily deep nesting of other
// key-value stores.
#[allow(clippy::box_collection, reason = "mass allow for https://fxbug.dev/381896734")]
enum StoreNode {
    Leaf(Option<Vec<u8>>),
    Branch(Box<HashMap<String, StoreNode>>),
}

/// Recursive item writer, which takes a `StoreNode` that may not necessarily be the root node, and
/// writes an entry to it.
fn write_item(
    store: &mut HashMap<String, StoreNode>,
    attempt: Item,
    path: &str,
) -> Result<(), WriteError> {
    // Validate the key.
    if !KEY_VALIDATION_REGEX.is_match(attempt.key.as_str()) {
        println!("Write error: INVALID_KEY, For key: {}", attempt.key);
        return Err(WriteError::InvalidKey);
    }

    // Write to the store, validating that the key did not already exist.
    match store.entry(attempt.key) {
        Entry::Occupied(entry) => {
            println!("Write error: ALREADY_EXISTS, For key: {}", entry.key());
            Err(WriteError::AlreadyExists)
        }
        Entry::Vacant(entry) => {
            let key = format!("{}{}", path, entry.key());
            match attempt.value {
                // Null entries are allowed.
                None => {
                    println!("Wrote value: NONE at key: {}", key);
                    entry.insert(StoreNode::Leaf(None));
                }
                Some(value) => match *value {
                    // If this is a nested store, recursively make a new store to insert at this
                    // position.
                    Value::Store(entry_list) => {
                        // Validate the value - absent stores, items lists with no children, or any
                        // of the elements within that list being empty boxes, are all not allowed.
                        if entry_list.items.is_some() {
                            let items = entry_list.items.unwrap();
                            if !items.is_empty() && items.iter().all(|i| i.is_some()) {
                                let nested_path = format!("{}/", key);
                                let mut nested_store = HashMap::<String, StoreNode>::new();
                                for item in items.into_iter() {
                                    write_item(&mut nested_store, *item.unwrap(), &nested_path)?;
                                }

                                println!("Created branch at key: {}", key);
                                entry.insert(StoreNode::Branch(Box::new(nested_store)));
                                return Ok(());
                            }
                        }

                        println!("Write error: INVALID_VALUE, For key: {}", key);
                        return Err(WriteError::InvalidValue);
                    }

                    // This is a simple leaf node on this branch.
                    Value::Bytes(value) => {
                        // Validate the value.
                        if value.is_empty() {
                            println!("Write error: INVALID_VALUE, For key: {}", key);
                            return Err(WriteError::InvalidValue);
                        }

                        println!("Wrote key: {}, value: {:?}", key, from_utf8(&value).unwrap());
                        entry.insert(StoreNode::Leaf(Some(value)));
                    }
                },
            }
            Ok(())
        }
    }
}

/// Creates a new instance of the server. Each server has its own bespoke, per-connection instance
/// of the key-value store.
async fn run_server(stream: StoreRequestStream) -> Result<(), Error> {
    // Create a new in-memory key-value store. The store will live for the lifetime of the
    // connection between the server and this particular client.
    let store = RefCell::new(HashMap::<String, StoreNode>::new());

    // Serve all requests on the protocol sequentially - a new request is not handled until its
    // predecessor has been processed.
    stream
        .map(|result| result.context("failed request"))
        .try_for_each(|request| async {
            // Match based on the method being invoked.
            match request {
                StoreRequest::WriteItem { attempt, responder } => {
                    println!("WriteItem request received");

                    // The `responder` parameter is a special struct that manages the outgoing reply
                    // to this method call. Calling `send` on the responder exactly once will send
                    // the reply.
                    responder
                        .send(write_item(&mut store.borrow_mut(), attempt, ""))
                        .context("error sending reply")?;
                    println!("WriteItem response sent");
                }
                StoreRequest::_UnknownMethod { ordinal, .. } => {
                    println!("Received an unknown method with ordinal {ordinal}");
                }
            }
            Ok(())
        })
        .await
}

// A helper enum that allows us to treat a `Store` service instance as a value.
enum IncomingService {
    Store(StoreRequestStream),
}

#[fuchsia::main]
async fn main() -> Result<(), Error> {
    println!("Started");

    // Add a discoverable instance of our `Store` protocol - this will allow the client to see the
    // server and connect to it.
    let mut fs = ServiceFs::new_local();
    fs.dir("svc").add_fidl_service(IncomingService::Store);
    fs.take_and_serve_directory_handle()?;
    println!("Listening for incoming connections");

    // The maximum number of concurrent clients that may be served by this process.
    const MAX_CONCURRENT: usize = 10;

    // Serve each connection simultaneously, up to the `MAX_CONCURRENT` limit.
    fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::Store(stream)| {
        run_server(stream).unwrap_or_else(|e| println!("{:?}", e))
    })
    .await;

    Ok(())
}

C++ (自然)

用戶端

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

伺服器

// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.

C++ (Wire)

用戶端

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

伺服器

// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.

fi-0194

fi-0195

fi-0196

fi-0200

fi-0201:未選取平台版本

如果編譯 FIDL 程式庫版本時未選擇版本,就會發生這項錯誤:

// fidlc --files test.fidl --out test.json
@available(platform="foo", added=1)
library test.bad.fi0201;

如要修正這個問題,請使用 --available 指令列旗標選擇版本:

// fidlc --files test.fidl --out test.json --available foo:1
@available(platform="foo", added=1)
library test.good.fi0201;

版本必須是大於或等於 1 的數字,或是特殊版本 NEXT 和 HEAD 之一。詳情請參閱 FIDL 版本管理說明文件。

fi-0202

fi-0203:已移除和已更換互斥

@available 屬性支援 removed 和 replaced 引數,但兩者無法同時使用:

@available(added=1)
library test.bad.fi0203;

protocol Foo {
    @available(removed=2, replaced=2)
    Foo();
};

如要修正錯誤,請刪除其中一個引數。如果您打算移除元素而不替換,請保留 removed 並刪除 replaced:

@available(added=1)
library test.good.fi0203a;

open protocol Foo {
    @available(removed=2)
    strict Foo();
};

或者,如果您要將元素替換為新定義,請保留 replaced 並刪除 removed:

@available(added=1)
library test.good.fi0203b;

open protocol Foo {
    @available(replaced=2)
    strict Foo();

    @available(added=2)
    flexible Foo();
};

removed 和 replaced 的意義相反,因此不適合搭配使用。當元素標示為 removed 時,fidlc 會驗證在相同版本中「沒有」新增替代元素。如果元素標示為 replaced,fidlc 會驗證是否已在相同版本中新增替代元素。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0204:無法取代程式庫

@available 屬性的 replaced 引數無法用於程式庫宣告:

@available(added=1, replaced=2)
library test.bad.fi0204;

請改用 removed 引數:

@available(added=1, removed=2)
library test.good.fi0204;

replaced 引數表示元素已由新定義取代。整個程式庫不支援這項功能,因為我們假設每個程式庫只有一組定義檔案。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0205:@available(removed=N) 無效

如果元素標示為 @available(removed=N),表示該元素無法再用於 N 版本。無法重複使用其名稱:

@available(added=1)
library test.bad.fi0204;

open protocol Foo {
    @available(removed=2)
    strict Bar();
    @available(added=2)
    flexible Bar();
};

如要將元素替換為新定義 (相同的 API 和 ABI),請使用 replaced 引數,而非 removed 引數:

@available(added=1)
library test.good.fi0204a;

open protocol Foo {
    @available(replaced=2)
    strict Bar();
    @available(added=2)
    flexible Bar();
};

如要移除元素並定義新的不相關元素 (不同的 API 和 ABI),請為新元素選擇不同的名稱:

@available(added=1)
library test.good.fi0204b;

open protocol Foo {
    @available(removed=2)
    strict Bar();
    @available(added=2)
    flexible NewBar();
};

如果您確實想重複使用名稱 (相同的 API,不同的 ABI),請使用 renamed 引數重新命名移除後的舊元素,釋出其原始名稱:

@available(added=1)
library test.good.fi0204c;

open protocol Foo {
    @available(removed=2, renamed="DeprecatedBar")
    strict Bar();

    @available(added=2)
    @selector("NewBar")
    flexible Bar();
};

請注意,在這種情況下,您必須使用 @selector,確保新方法具有不同的 ABI。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0206:@available(replaced=N) 無效

如果元素標示為 @available(replaced=N),表示該元素已由標示為 @available(added=N) 的新定義取代。如果 FIDL 編譯器找不到這類定義,就會回報錯誤:

@available(added=1)
library test.bad.fi0205;

open protocol Foo {
    @available(replaced=2)
    strict Bar();
};

如果您不打算取代元素,請使用 removed 引數,而非 replaced 引數:

@available(added=1)
library test.good.fi0205a;

open protocol Foo {
    @available(removed=2)
    strict Bar();
};

如要取代元素,請新增取代定義:

@available(added=1)
library test.good.fi0205b;

open protocol Foo {
    @available(replaced=2)
    strict Bar();
    @available(added=2)
    flexible Bar();
};

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0207:類型形狀整數溢位

FIDL 類型的大小不得超過 uint32:

library test.bad.fi0207;

type Foo = struct {
    bytes array<uint64, 536870912>;
};

如要修正錯誤,請使用較小的陣列大小:

library test.good.fi0207;

type Foo = struct {
    bytes array<uint64, 100>;
};

實際上,FIDL 類型應遠小於 232 個位元組,因為這類類型通常是透過 Zircon 管道傳送,而 Zircon 管道的限制為每則訊息 64 KiB。

fi-0208:預留平台

FIDL 會保留特定平台名稱。例如,「無版本」平台會保留給不使用版本控制的程式庫:

@available(platform="unversioned", added=1)
library test.bad.fi0208;

請改用其他平台名稱:

@available(platform="foo", added=1)
library test.good.fi0208;

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0209: Reserved fields are not allowed

FIDL 不再支援 reserved 表格或聯集欄位:

library test.bad.fi0209;

type User = table {
    1: reserved;
    2: email string;
};

保留欄位的主要目的是避免意外重複使用序數。有了 FIDL 版本管理,這就不再是問題。您可以為舊欄位加上 @available(removed=N) 註解,同時將這些欄位 (及其序數) 保留在來源檔案中:

@available(added=1)
library test.good.fi0209a;

type User = table {
    @available(removed=2)
    1: name string;
    2: email string;
};

reserved 的另一個用途是記錄序數的預期未來用途。在這種情況下,請考慮在不穩定的 API 級別 HEAD 定義欄位:

@available(added=1)
library test.good.fi0209b;

type User = table {
    @available(added=HEAD)
    1: name string;
    2: email string;
};

如要將 reserved 用於其他用途,建議改為留下註解:

library test.good.fi0209c;

type User = table {
    // We skip ordinal 1 because...
    2: email string;
};

fi-0210:@discoverable 用戶端或伺服器引數中的位置無效

可供偵測通訊協定允許的 client 或 server 位置,必須是以半形逗號分隔的 platform 和 external 清單 (可能為空白)。也就是下列其中一項: - "" - "platform" - "external" - "platform,external" - "external,platform"

library test.bad.fi0210;

@discoverable(server="platform,canada")
protocol Example {};

請確認下列引數正確無誤:

library test.good.fi0210;

@discoverable(server="platform")
protocol Example {};

fi-0211:無法重新命名元素

@available 屬性的 renamed 引數只能用於宣告成員,不能用於宣告本身:

@available(added=1)
library test.bad.fi0211;

@available(replaced=2, renamed="Bar")
type Foo = struct {};

@available(added=2)
type Bar = struct {};

請移除舊的宣告並新增宣告,不要重新命名:

@available(added=1)
library test.good.fi0211;

@available(removed=2)
type Foo = struct {};

@available(added=2)
type Bar = struct {};

只有成員支援重新命名,因為 FIDL 編譯器可以比較其 ABI 身分 (例如資料表序數),確保正確完成。

下列元素也不允許使用 renamed 引數:

  • library:您無法從內部重新命名程式庫,因為 FIDL 工具鍊會假設每個程式庫只有一個名稱。建議您改為建立新程式庫並指定所需名稱,然後將使用者遷移至該程式庫。
  • compose:您無法重新命名通訊協定組合,因為組合一開始就沒有名稱。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0212:已重新命名,但未更換或移除

@available 引數 renamed 不得單獨使用。必須與 replaced 或 removed 引數搭配使用:

@available(added=1)
library test.bad.fi0212;

protocol Foo {
    @available(renamed="New")
    Old();
};

如果只想重新命名 N 版本的元素,請使用 replaced=N,並定義標示為 added=N 的新名稱替換:

@available(added=1)
library test.good.fi0212a;

protocol Foo {
    @available(replaced=2, renamed="New")
    Old();

    @available(added=2)
    @selector("Old")
    New();
};

在這種情況下,替換方法必須覆寫 @selector,以確保 ABI 相容性。

或者,如果您想移除 N 版本的元素,並在移除後以其他名稱參照該元素,請使用 removed=N:

@available(added=1)
library test.good.fi0212b;

protocol Foo {
    @available(removed=2, renamed="DeprecatedOld")
    Old();
};

在這種情況下,只有在指定多個版本 (例如 --available test:1,2) 時,才會使用新名稱,因為這是納入元素,同時指定已移除該元素版本的唯一方法。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0213:重新命名為相同名稱

使用 @available 引數 renamed 重新命名元素時,新名稱不得與元素原始名稱相同:

@available(added=1)
library test.bad.fi0213;

type Foo = table {
    @available(replaced=2, renamed="bar")
    1: bar string;
    @available(added=2)
    1: bar string:10;
};

如要修正錯誤,請移除 renamed 引數:

@available(added=1)
library test.good.fi0213a;

type Foo = table {
    @available(replaced=2)
    1: bar string;
    @available(added=2)
    1: bar string:10;
};

或者,保留 renamed 引數,但選擇其他名稱:

@available(added=1)
library test.good.fi0213b;

type Foo = table {
    @available(replaced=2, renamed="baz")
    1: bar string;
    @available(added=2)
    1: baz string:10;
};

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0214:@available(removed=N, renamed="NewName")無效

這與 fi-0205:@available(removed=N) 無效類似,但適用於涉及 renamed 引數的情況。

如果元素標示為 @available(removed=N, renamed="NewName"),表示該元素在 N 版本中無法再使用,且在移除後重新命名為「NewName」。您無法將「NewName」用於其他用途:

@available(added=1)
library test.bad.fi0214;

open protocol Foo {
    @available(removed=2, renamed="NewName")
    strict OldName();

    @available(added=2)
    flexible NewName();
};

如要重新命名元素,同時保留其 ABI,請使用 replaced 引數,而非 removed 引數:

@available(added=1)
library test.good.fi0214a;

open protocol Foo {
    @available(replaced=2, renamed="NewName")
    strict OldName();

    @available(added=2)
    @selector("OldName")
    flexible NewName();
};

請注意,在這種情況下,您必須使用 @selector,確保重新命名的方法具有相同的 ABI。

如果希望新元素使用不同的 ABI,請保留 removed,並確保 renamed 引數和新元素使用不同的名稱:

@available(added=1)
library test.good.fi0214b;

open protocol Foo {
    @available(removed=2, renamed="NewName")
    strict OldName();

    @available(added=2)
    strict DifferentName();
};

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0215:@available(replaced=N, renamed="NewName")無效

這與 fi-0206:@available(replaced=N) 無效類似,但適用於涉及 renamed 引數的情況。

如果元素標示為 @available(replaced=N, renamed="NewName"),表示該元素已由名為「NewName」的新定義取代,並標示為 @available(added=N)。如果 FIDL 編譯器找不到這類定義,就會回報錯誤:

@available(added=1)
library test.bad.fi0215;

open protocol Foo {
    @available(replaced=2, renamed="NewName")
    strict OldName();
};

如要修正錯誤,請使用新名稱定義元素:

@available(added=1)
library test.good.fi0215;

open protocol Foo {
    @available(replaced=2, renamed="NewName")
    strict OldName();

    @available(added=2)
    @selector("OldName")
    strict NewName();
};

請注意,您必須使用 @selector,確保重新命名的方法具有相同的 ABI。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0216:@available(removed=N) (ABI) 無效

這與 fi-0205:@available(removed=N) 無效類似,但不是重複使用元素名稱,而是其 ABI:

@available(added=1)
library test.bad.fi0216;

open protocol Foo {
    @available(removed=2)
    strict Bar();

    @available(added=2)
    @selector("Bar")
    flexible Qux();
};

如果您有意取代元素的 ABI,請使用引數 replaced 和 renamed (而非 removed) 明確指定:

@available(added=1)
library test.good.fi0216a;

open protocol Foo {
    @available(replaced=2, renamed="Qux")
    strict Bar();

    @available(added=2)
    @selector("Bar")
    flexible Qux();
};

如果您無意重複使用 ABI,請選擇其他 ABI。在這種情況下,我們可以移除 @selector 屬性,並根據方法名稱使用預設選取器:

@available(added=1)
library test.good.fi0216b;

open protocol Foo {
    @available(removed=2)
    strict Bar();

    @available(added=2)
    flexible Qux();
};

不只是方法,其他成員也可能發生這項錯誤。如果是位元和列舉成員,ABI 是整數值。對於 struct 成員,ABI 是位元組偏移。如果是資料表和聯集成員,ABI 則是序數。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0217:@available(replaced=N) (ABI) 無效

這與 fi-0206:@available(replaced=N) 無效類似,但僅適用於找到的替代項目只與元素名稱相符,而非 ABI:

@available(added=1)
library test.bad.fi0217;

open protocol Foo {
    @available(replaced=2)
    strict Bar();

    @available(added=2)
    @selector("NotBar")
    flexible Bar();
};

如果您想取代元素,請確認其 ABI 相符。在這種情況下,由於這兩種方法已有相同名稱,因此我們可以移除 @selector 屬性:

@available(added=1)
library test.good.fi0217a;

open protocol Foo {
    @available(replaced=2)
    strict Bar();

    @available(added=2)
    flexible Bar();
};

如果無意取代 ABI,請使用 removed,而非 replaced。在這種情況下,我們也必須選擇其他名稱,以免與舊名稱衝突:

@available(added=1)
library test.good.fi0217b;

open protocol Foo {
    @available(removed=2)
    strict Bar();

    @available(added=2)
    flexible NotBar();
};

如果您確實想重複使用名稱,但不想重複使用 ABI,請使用 removed 而非 replaced,並使用 renamed 重新命名移除後的舊元素,釋出其原始名稱:

@available(added=1)
library test.good.fi0217c;

open protocol Foo {
    @available(removed=2, renamed="DeprecatedBar")
    strict Bar();

    @available(added=2)
    @selector("NotBar")
    flexible Bar();
};

除了方法以外,其他成員也可能發生這項錯誤。如果是位元和列舉成員,ABI 是整數值。對於 struct 成員,ABI 是位元組偏移。如果是表格和聯集成員,ABI 則是序數。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0218:無效的修飾符可用性引數

在 FIDL 版本管理修飾符語法中,您只能使用引數 added 和 removed。不允許使用其他 @available 引數,例如 deprecated:

@available(added=1)
library test.bad.fi0218;

type Foo = resource(deprecated=2) struct {};

如要修正錯誤,請移除不支援的引數:

@available(added=1)
library test.good.fi0218;

type Foo = resource struct {};

與宣告和成員不同,修飾符沒有自己的生命週期,因此淘汰、替換和重新命名等概念對修飾符沒有意義。只能新增和移除修飾符。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0219:無法變更方法嚴格程度

FIDL 版本管理修飾符語法可讓您新增或移除 strict 和 flexible 修飾符。不過,如果雙向方法未使用 error 語法,則不允許這樣做,因為這類變更會破壞 ABI:

@available(added=1)
library test.bad.fi0219;

open protocol Foo {
    strict(removed=2) flexible(added=2) Method() -> ();
};

請改為移除嚴格方法,並新增名稱不同的彈性方法來取代:

@available(added=1)
library test.good.fi0219a;

open protocol Foo {
    @available(removed=2)
    strict Method() -> ();

    @available(added=2)
    flexible NewMethod() -> ();
};

或者,如果您想為新的 ABI 重複使用方法名稱,可以使用 renamed 引數和 @selector 屬性:

@available(added=1)
library test.good.fi0219b;

open protocol Foo {
    @available(removed=2, renamed="StrictMethod")
    strict Method() -> ();

    @available(added=2)
    @selector("FlexibleMethod")
    flexible Method() -> ();
};

不允許在沒有錯誤語法的情況下變更雙向方法的嚴格程度,因為這會變更回應的形式。如果雙向方法具有彈性或使用錯誤語法,FIDL 會自動產生包裝回應的結果聯集。因此,只有在雙向方法含有錯誤語法時,才能安全地變更嚴格程度。

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0220:在版本範圍內找不到名稱

如果找不到特定版本的名稱,但在其他版本中找到,就會發生這項錯誤。舉例來說,在第 1 版新增的元素 Foo 無法參照在第 2 版新增的另一個元素 Bar,因為第 1 版不存在 Bar:

@available(added=1)
library test.bad.fi0220;

alias Foo = Bar;

@available(added=2)
type Bar = struct {};

在這種情況下,我們可以修正錯誤,新增 @available 屬性,讓 Foo 也在第 2 版中新增:

@available(added=1)
library test.good.fi0220;

@available(added=2)
alias Foo = Bar;

@available(added=2)
type Bar = struct {};

如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。

fi-0221:resource 出現在加上 @no_resource 註解的宣告中

如果宣告具有 @no_resource 屬性,但其中某些項目具有 resource 修飾符,就會發生這項錯誤。

library test.bad.fi0221;

@no_resource
open protocol Foo {
    flexible Bar(resource struct {
        a uint8;
    });
};

如要修正錯誤,您可以移除屬性或資源修飾符。

library test.good.fi0221;

@no_resource
open protocol Foo {
    flexible Bar(struct {
        a uint8;
    });
};

fi-0222:@no_resource 是實驗屬性

@no_resource 註解目前仍在實驗階段,僅供少數特定通訊協定使用。

library test.bad.fi0222;

@no_resource
type Foo = struct {
    a uint32;
};

註解會啟用新的編譯器錯誤,但不會對語意產生任何影響。直接移除應該沒問題。

library test.good.fi0222;

type Foo = struct {
    a uint32;
};

fi-0223:通訊協定具有 @no_resource 屬性,因此無法組成其他通訊協定

如果加上 @no_resource 註解的通訊協定,與未加上該註解的通訊協定組成另一個通訊協定,就會發生這項錯誤。

library test.bad.fi0223;

open protocol A {
    flexible F();
};

@no_resource
open protocol B {
    compose A;
    flexible G();
};

如要修正,請將 @no_resource 新增至組合通訊協定。

library test.good.fi0223;

@no_resource
open protocol A {
    flexible F();
};

@no_resource
open protocol B {
    compose A;
    flexible G();
};

fi-0224:不得為非零值

如果嘗試將某個類型標示為非零,但該類型不能為非零,就會發生這項錯誤。

library test.bad.fi0224;

type Bar = struct {
    foo float32:nonzero;
};

如要修正這項錯誤,請移除非零限制:

library test.good.fi0224;

type Bar = struct {
    foo float32;
};

只有整數 FIDL 類型可以限制為非零值。

fi-0225:類型不得標示為非零兩次

如果某個類型兩次設為非零,就會發生這項錯誤。通常,當類型在使用和宣告位置都標示為非零時,就會發生這種情況。

library test.bad.fi0225;

alias MyAlias = int32:nonzero;

type MyStruct = struct {
    my_member MyAlias:nonzero;
};

如要修正這個錯誤,請只將類型設為非零一次。

例如,您可以從使用網站中移除 :nonzero。

library test.good.fi0225a;

alias MyAlias = int32:nonzero;

type MyStruct = struct {
    my_member MyAlias;
};

您也可以從別名宣告中移除 :nonzero。

library test.good.fi0225b;

alias MyAlias = int32;

type MyStruct = struct {
    my_member MyAlias:nonzero;
};

fi-0226: Invalid value for non-zero constant

如果常數值的基礎類型限制為非零值,則常數值不得為零:

library test.bad.fi0226;

const FOO uint8:nonzero = 0;
const BAR int32:nonzero = 0;

如要修正問題,可以將值變更為符合類型範圍的值:

library test.good.fi0226a;

const FOO uint8:nonzero = 1;
const BAR int32:nonzero = 4;

或者,您也可以變更類型來容納零值:

library test.good.fi0226b;

const FOO uint8 = 0;
const BAR int32 = 0;