本文列出 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}";
您必須以十六進制指定有效的 萬國碼碼點,範圍為 0 到 10FFFF。每個 16 進位數字必須為 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 ID 可包含英數字元和底線 (具體為 A-Z
、a-z
、0-9
和 _
),此外,每個 ID 開頭都必須是英文字母,結尾則為英文字母或數字。
library test.bad.fi0010a;
// Foo_ is not a valid identifier because it ends with '_'.
type Foo_ = struct {
value uint64;
};
如要修正這個問題,請變更 ID,確保其只包含有效字元、開頭為英文字母,結尾為英文字母或數字。
library test.good.fi0010a;
type Foo = struct {
value uint64;
};
如果將多部分 (點) ID 傳遞至屬性,也可能發生此錯誤。
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:無效的包裝類型
如果傳遞至枚舉或位元宣告的值不是類型的 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;
};
與結構體不同,資料表和聯集可讓您對內容進行向後相容的變更。為啟用此功能,您需要使用一致的值 (序數) 來識別資料表欄位或聯集變數。為避免混淆,並在變更資料表或聯集時避免不小心變更序數,請務必明確指定序數。
fi-0017:序數超出邊界
表格和聯集的序號必須是有效的未簽署 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:嚴格位元、列舉或聯集不得為空
嚴格位元、列舉或聯集不得有零個成員:
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;
};
空位元、列舉或聯集不含任何資訊,因此通常不應用於 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 型別時,就會發生此錯誤。舉例來說,在結構體宣告中,將屬性放在欄位名稱後方,但在欄位類型之前,會將屬性與欄位類型建立關聯,而非與欄位本身建立關聯。如果欄位的類型是已存在的類型,且以名稱參照,則無法套用其他屬性。
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;
};
如果您想將屬性套用至欄位,則應將屬性移至欄位名稱之前。
屬性可套用至宣告的類型。也就是說,如果結構體欄位或其他類似宣告的類型是匿名類型,而非 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
是指該列舉成員還是在匯入的程式庫中宣告的 const,就會變得模糊不清。
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:選用聯集成員
聯盟成員不得為選用項目:
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 不允許組合成員為選用,因為這可能會導致以多種方式表示相同的值。舉例來說,如果聯集有 3 個選用成員,則會有 6 個狀態 (每個成員 2 個)。相反地,您應使用類型為 struct {}
的第四個成員來模擬此類型,或是將整體聯集設為 Foo:optional
的選用項目。
fi-0050:禁止使用已淘汰的結構體預設語法
先前,FIDL 允許在 struct
成員上設定預設值:
library test.bad.fi0050;
type MyStruct = struct {
field int64 = 20;
};
自 RFC-0160:移除對 FIDL 結構體預設值的支援 起,系統將禁止以下行為:
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
的例項,以此類推。解決這個問題的方法是透過選項在這個鏈結中加入「中斷點」,這樣一來,您可以選擇編碼另一個 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:常數類型無效
並非所有類型都能用於 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 運算子
二元或運算子只能用於原始值:
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:類型別名和新類型中的 Newtype 尚未完全實作,因此無法使用:
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:預期值,但實際值為類型
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:位元或列舉值類型不正確
使用 bits
或 enum
變體做為 const
宣告中的值時,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;
};
請只選擇並註解用於特定網域的「unknown」值的元素:
library test.good.fi0071a;
type MyEnum = flexible enum : int8 {
@unknown
UNKNOWN = 0;
OTHER = 1;
};
@unknown
屬性的目的,是讓使用者從含有使用者定義不明值的 strict enum
順利轉換至含有不明值的 flexible enum
,並由 FIDL 處理:
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:重複的方法序號
這個錯誤通常發生在您使用 @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:無效的結構體成員類型
當您嘗試為不支援的類型設定預設結構體值時,就會發生這個錯誤。只有數值和布林值類型可設定預設結構體值。
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:表格序號過大
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 封套的額外負擔,以便讓欄位為選填欄位。這可讓資料表的每個欄位出現或缺席,並透過新增或移除欄位來進化資料表,但代價是記憶體開銷會比結構體高出許多。
一般來說,您可以避免在資料表中使用小型、精細的欄位,藉此避免發生這項錯誤並減少額外負擔。您可以將預期需要同時加入或移除至結構體的元素分組,然後將這些元素用作資料表的欄位。這麼做可減少開銷,並避免序數用盡,但會影響部分可擴充性。
這項錯誤已在 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
宣告成員的預設值必須與各自成員的宣告類型相符:
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;
};
如要修正錯誤,請確認成員具有某些通訊協定 P
的 client_end: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 中尚未完成的功能。這些類別最初是在 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 或開放式通訊協定
封閉式通訊協定不得包含彈性的單向方法:
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 識別碼的規則。
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:新增圖書館可用性缺少
如果您在未提供 added
引數的情況下,將 @available
屬性新增至程式庫,就會發生這個錯誤。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 程式庫識別碼。
@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:可用性與父項衝突
當您在非 library
宣告中新增 @availability
屬性,且與 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 食譜:選用性
您可以透過新增 :optional
限制,讓特定 FIDL 類型變成選用,且不會變更所含訊息的線路形狀。此外,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>
類型儲存葉結點,另一個則會以其他巢狀儲存的形式儲存分支節點。
推理
這裡我們會看到幾種 optionality 的用法,我們可以宣告可能存在或不存在的型別。FIDL 中的選用性有三種:
- 具有此類型的類型一律會在網路上離線儲存,因此具有內建方式,可透過空值封套描述「缺席」。為這些類型啟用選用性不會影響所包含訊息的線路形狀,只會變更哪些值適用於該特定類型。您可以透過新增
:optional
限制,將union
、vector<T>
、client_end
、server_end
和zx.Handle
類型設為選用。將value
union
設為選用項目後,我們就能以缺少value
的形式,引入標準的「空值」項目。這表示空白的bytes
和缺少/空白的store
屬性是無效的值。 - 與上述類型不同,
struct
版面配置沒有可儲存空值標頭的額外空間。因此,您必須將其包裝在信封中,以便變更所附帶訊息的線上傳輸形狀。為確保這個線路修改效果易於解讀,Item
struct
類型必須包裝在box<T>
類型範本中。 - 最後,
table
版面配置一律為選用項目。缺少的table
只是沒有任何成員集。
樹狀圖是自然的自我參照資料結構:樹狀圖中的任何節點都可能包含純資料的葉節點 (在本例中為字串),或包含更多節點的子樹狀圖。這需要遞迴:Item
的定義現在會遞迴地依附自身!在 FIDL 中表示遞迴類型可能有點棘手,尤其是因為目前的支援有點受限。只要在透過自參照建立的循環中,至少有一個選用型別,我們就能支援這類型別。舉例來說,我們在此將 items
struct
成員定義為 box<Item>
,藉此中斷包含循環。
這些變更也大量使用匿名類型,也就是在使用點內嵌宣告而非命名的類型,以及頂層 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: { write_items: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // 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. write_nested: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // A list of keys, all of which will be populated as null entries. write_null: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, }, }
伺服器
// 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", }, ], }
運作範圍
// 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", }, { dictionary: "diagnostics", from: "parent", to: "all", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], 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::*, lazy_static::lazy_static, regex::Regex, std::cell::RefCell, std::collections::hash_map::Entry, std::collections::HashMap, std::str::from_utf8, }; lazy_static! { static ref KEY_VALIDATION_REGEX: Regex = 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.
HLCPP
用戶端
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
伺服器
// TODO(https://fxbug.dev/42060656): HLCPP 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:無法繫結兩次
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:預期類型
當 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:無法兩次限制
禁止針對已透過 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 類型不得為選填
box<T>
形式的類型無法套用 optional
限制條件:
library test.bad.fi0169;
type Color = struct {
red byte;
green byte;
blue byte;
};
type MyStruct = struct {
maybe_color box<Color>:optional;
};
根據定義,箱型類型為選用,因此不必新增額外限制,這項限制也沒有必要:
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 食譜:選用性
您可以透過新增 :optional
限制,讓特定 FIDL 類型變成選用,且不會變更所含訊息的線路形狀。此外,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>
類型儲存葉結點,另一個則會以其他巢狀儲存的形式儲存分支節點。
推理
這裡我們會看到幾種 optionality 的用法,我們可以宣告可能存在或不存在的型別。FIDL 中的選用性有三種:
- 具有此類型的類型一律會在網路上離線儲存,因此具有內建方式,可透過空值封套描述「缺席」。為這些類型啟用選用性不會影響所包含訊息的線路形狀,只會變更哪些值適用於該特定類型。您可以透過新增
:optional
限制,將union
、vector<T>
、client_end
、server_end
和zx.Handle
類型設為選用。將value
union
設為選用項目後,我們就能以缺少value
的形式,引入標準的「空值」項目。這表示空白的bytes
和缺少/空白的store
屬性是無效的值。 - 與上述類型不同,
struct
版面配置沒有可儲存空值標頭的額外空間。因此,您必須將其包裝在信封中,以便變更所附帶訊息的線上傳輸形狀。為確保這個線路修改效果易於解讀,Item
struct
類型必須包裝在box<T>
類型範本中。 - 最後,
table
版面配置一律為選用項目。缺少的table
只是沒有任何成員集。
樹狀圖是自然的自我參照資料結構:樹狀圖中的任何節點都可能包含純資料的葉節點 (在本例中為字串),或是包含更多節點的子樹狀圖。這需要遞迴:Item
的定義現在會遞迴地依附自身!在 FIDL 中表示遞迴類型可能有點棘手,尤其是因為目前的支援有點受限。只要在透過自參照建立的循環中,至少有一個選用型別,我們就能支援這類型別。舉例來說,我們在此將 items
struct
成員定義為 box<Item>
,藉此中斷包含循環。
這些變更也大量使用匿名類型,也就是在使用點內嵌宣告而非命名的類型,以及頂層 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: { write_items: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // 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. write_nested: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // A list of keys, all of which will be populated as null entries. write_null: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, }, }
伺服器
// 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", }, ], }
運作範圍
// 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", }, { dictionary: "diagnostics", from: "parent", to: "all", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], 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::*, lazy_static::lazy_static, regex::Regex, std::cell::RefCell, std::collections::hash_map::Entry, std::collections::HashMap, std::str::from_utf8, }; lazy_static! { static ref KEY_VALIDATION_REGEX: Regex = 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.
HLCPP
用戶端
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
伺服器
// TODO(https://fxbug.dev/42060656): HLCPP 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:Newtype 無法受到限制
不允許限制 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;
};
這項限制可簡化新類型的設計。對於受限的新型別,API 和 ABI 應如何運作目前尚不清楚 (例如,限制應套用至新型別本身,還是應流向基礎類型?)。
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:萬國碼逸出序列缺少大括號
字串常值中的萬國碼逸出序列必須在大括號中指定碼點:
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:無法使用盒裝
除了結構體之外,其他類型都無法封箱。舉例來說,原始類型無法封箱:
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 食譜:選用性
您可以透過新增 :optional
限制,讓特定 FIDL 類型變成選用,且不會變更所含訊息的線路形狀。此外,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>
類型儲存葉結點,另一個則會以其他巢狀儲存的形式儲存分支節點。
推理
這裡我們會看到幾種 optionality 的用法,我們可以宣告可能存在或不存在的型別。FIDL 中的選用性有三種:
- 具有此類型的類型一律會在網路上離線儲存,因此具有內建方式,可透過空值封套描述「缺席」。為這些類型啟用選用性不會影響所包含訊息的線路形狀,只會變更哪些值適用於該特定類型。您可以透過新增
:optional
限制,將union
、vector<T>
、client_end
、server_end
和zx.Handle
類型設為選用。將value
union
設為選用項目後,我們就能以缺少value
的形式,引入標準的「空值」項目。這表示空白的bytes
和缺少/空白的store
屬性是無效的值。 - 與上述類型不同,
struct
版面配置沒有可儲存空值標頭的額外空間。因此,您必須將其包裝在信封中,以便變更所附帶訊息的線上傳輸形狀。為確保這個線路修改效果易於解讀,Item
struct
類型必須包裝在box<T>
類型範本中。 - 最後,
table
版面配置一律為選用項目。缺少的table
只是沒有任何成員集。
樹狀圖是自然的自我參照資料結構:樹狀圖中的任何節點都可能包含純資料的葉節點 (在本例中為字串),或包含更多節點的子樹狀圖。這需要遞迴:Item
的定義現在會遞迴地依附自身!在 FIDL 中表示遞迴類型可能有點棘手,尤其是因為目前的支援有點受限。只要在透過自參照建立的循環中,至少有一個選用型別,我們就能支援這類型別。舉例來說,我們在此將 items
struct
成員定義為 box<Item>
,藉此中斷包含循環。
這些變更也大量使用匿名類型,也就是在使用點內嵌宣告而非命名的類型,以及頂層 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: { write_items: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // 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. write_nested: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, // A list of keys, all of which will be populated as null entries. write_null: { type: "vector", max_count: 16, element: { type: "string", max_size: 64, }, }, }, }
伺服器
// 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", }, ], }
運作範圍
// 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", }, { dictionary: "diagnostics", from: "parent", to: "all", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], 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::*, lazy_static::lazy_static, regex::Regex, std::cell::RefCell, std::collections::hash_map::Entry, std::collections::HashMap, std::str::from_utf8, }; lazy_static! { static ref KEY_VALIDATION_REGEX: Regex = 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.
HLCPP
用戶端
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
伺服器
// TODO(https://fxbug.dev/42060656): HLCPP 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 管道傳送,而該管道每則訊息的大小上限為 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:不允許使用保留欄位
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 是整數值。對於結構體成員,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 是整數值。對於結構體成員,ABI 是位元組偏移量。對於資料表和聯合體成員,ABI 是序號。
如要進一步瞭解版本管理,請參閱「FIDL 版本管理」。
fi-0218:無效的修飾符可用性引數
在 FIDL 版本控制修飾符語法中,您只能使用 added
和 removed
參數。不允許 deprecated
等其他 @available
引數:
@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
,因為 Bar
不存在於第 1 版中:
@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();
};