FIDL 語言規格

本文件是 Fuchsia 介面定義語言 (FIDL) 的規格。

如要進一步瞭解 FIDL 的整體用途、目標和需求,請參閱「總覽」。

此外,請參閱修改後的 FIDL 文法說明

語法

FIDL 提供宣告資料類型和通訊協定的語法。這些宣告會收集至程式庫以進行發布。

FIDL 宣告會以 UTF-8 文字檔儲存。每個檔案都包含一系列以半形分號分隔的宣告。FIDL 檔案或程式庫中 FIDL 檔案之間的宣告順序無關。

留言

FIDL 註解以兩個正斜線 (//) 為開頭,然後繼續至該行結尾。開頭為三個正斜線 (///) 的註解稱為說明文件註解,這類註解會在產生的繫結中以註解的形式傳送。

// this is a comment
/// and this one is too, but it also ends up in the generated code
type MyStruct = struct { // plain comment
    f int32; // as is this one
}; // and this is the last one!

關鍵字

以下字詞在 FIDL 中具有特殊意義:

ajar, alias, as, bits, closed, compose, const, enum, error, false, flexible,
library, open, optional, protocol, resource, service, strict, struct, table,
true, type, union, using.

然而,FIDL 沒有保留關鍵字。例如:

// Declaring a struct named "struct" is allowed, though confusing.
type struct = struct {};

// Declaring a table field named "strict" is a more reasonable example.
type Options = table {
    1: strict bool;
};

ID

圖書館名稱

FIDL「程式庫名稱」標籤的「FIDL 程式庫」。其中包含一或多個以點 (.) 分隔的元件。每個元件都必須與規則運算式 [a-z][a-z0-9]* 相符。換句話說:程式庫名稱元件的開頭須為小寫英文字母,可以包含小寫英文字母和數字。

// A library named "foo".
library foo;

ID

FIDL ID 標籤宣告及其成員。這些必須與規則運算式 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])? 相符。換句話說:ID 開頭必須是英文字母,可包含英文字母、數字和底線,但結尾不得為底線。

// A struct named "Foo".
type Foo = struct {};

FIDL ID 須區分大小寫。不過,ID 必須有專屬的標準格式,否則 FIDL 編譯器會因 fi-0035:正規名稱衝突而失敗。ID 的標準格式可透過轉換為 snake_case 取得。

有效 ID

FIDL 一律會在目前的程式庫範圍內尋找不合格的符號。如要參照其他程式庫中的符號,您必須使用程式庫名稱或別名進行評估。

objects.fidl:

library objects;
using textures as tex;

protocol Frob {
    // "Thing" refers to "Thing" in the "objects" library
    // "tex.Color" refers to "Color" in the "textures" library
    Paint(struct { thing Thing; color tex.Color; });
};

type Thing = struct {
    name string;
};

textures.fidl:

library textures;

type Color = struct {
    rgba uint32;
};

解析度演算法

FIDL 會使用下列演算法來解析 ID。「嘗試解決」步驟失敗時,系統會繼續執行下一個步驟。當「解決」步驟失敗時,編譯器會產生錯誤。

  • 不符合資格:
    1. 請嘗試解析為目前程式庫中的宣告。
    2. 請嘗試以內建宣告的形式發出,例如 bool 指的是 fidl.bool
    3. 做為情境位元/列舉成員解析,例如 zx.handle:CHANNEL 中的 CHANNEL 參照 zx.ObjType.Channel
  • 如果是 X.Y
    1. 請嘗試將 X 解析為目前程式庫中的宣告:
      1. 解決 YX 的成員。
    2. X 解析為程式庫名稱或別名。
      1. X 中的宣告解析為 Y
  • 如果有效條件為 x.Y.Z,其中 x 代表一或多個元件:
    1. 嘗試將 x.Y 解析為程式庫名稱或別名:
      1. x.Y 中的宣告解析為 Z
    2. x 解析為程式庫名稱或別名:
      1. Y 解析為 x 中的宣告:
        1. 解決 Zx.Y 的成員。

完整名稱

FIDL 使用完整名稱 (縮寫「FQN」) 來明確指名宣告和成員。程式庫名稱的 FQN 常數、斜線 /、宣告 ID,以及選用點 . 和成員 ID。例如:

  • fuchsia.io/MAX_BUF 參照程式庫 fuchsia.io 中的 MAX_BUF 常數。
  • fuchsia.process/Launcher.Launch 是指程式庫 fuchsia.processLauncher 通訊協定中的 Launch 方法。

FQN 會用於錯誤訊息、FIDL JSON 中繼表示法、方法選取器,以及說明文件註解交叉參照中。

常值

FIDL 支援下列種類的文字:

  • 布林值:truefalse
  • 整數:十進位 (123)、十六進位 (0xA1B2)、八進位 (0755)、二進位 (0b101)。
    • 只有小數文字可以是負數。
  • 浮點:1.23-0.011e52.0e-3
    • 只能使用 ee-,不得使用 e+
  • 字串:"hello""\\ \" \n \r \t \u{1f642}"

數字常值中的所有字母 (例如十六進位數字) 都不區分大小寫。

常數

FIDL 可讓您為支援常值 (布林值、整數、浮點和字串)、位元和列舉的所有類型定義常數。例如:

const ENABLED_FLAG bool = true;
const OFFSET int8 = -33;
const ANSWER uint16 = 42;
const ANSWER_IN_BINARY uint16 = 0b101010;
const POPULATION_USA_2018 uint32 = 330000000;
const DIAMOND uint64 = 0x183c7effff7e3c18;
const FUCHSIA uint64 = 4054509061583223046;
const USERNAME string = "squeenze";
const MIN_TEMP float32 = -273.15;
const CONVERSION_FACTOR float64 = 1.41421358;
const MY_DRINK Beverage = Beverage.WATER;
const FEATURES InfoFeatures = InfoFeatures.WLAN | InfoFeatures.SYNTH;

常數運算式可以是常值、其他常數的參照、位元或列舉成員的參照,或由管道字元 (|) 分隔的位元成員組合。FIDL 不支援任何其他算術運算式,例如 1 + 2

宣告分隔符

FIDL 會使用分號 (;) 分隔檔案中的相鄰宣告,與 C 類似。

程式庫

程式庫是 FIDL 宣告的已命名容器。

// library identifier separated by dots
library fuchsia.composition;

// "using" to import library "fuchsia.mem"
using fuchsia.mem;

// "using" to import library "fuchsia.geometry" under the alias "geo"
using fuchsia.geometry as geo;

程式庫使用 using 宣告匯入其他程式庫。您可以使用程式庫名稱來完成匯入程式庫中的符號,如 fuchsia.mem.Range 所示。使用 using ... as 語法時,您必須使用別名限定符號,就像在 geo.Rect 中一樣 (fuchsia.geometry.Rect 無法運作)。

libraryusing 宣告的範圍僅限於單一檔案。FIDL 程式庫中的每個檔案都必須重新陳述 library 宣告和檔案需要的任何 using 宣告。

FIDL 編譯器不需要任何特定目錄結構,但每個 FIDL 程式庫通常會歸類在以程式庫命名的專屬目錄中。含有多個檔案的程式庫一般會有一個 Overview.fidl 檔案,包含 library 宣告、屬性和說明文件註解。

程式庫的名稱可用於語言繫結中做為命名空間。例如,C++ 命名空間 fuchsia_ui 中 FIDL 程式庫 fuchsia.ui 的 C++ 繫結產生器地點宣告。同樣地,Rust 繫結產生器會產生名為 fidl_fuchsia_ui 的 Crate。

類型和類型宣告

FIDL 支援多種內建類型,以及新類型的宣告 (例如結構體、聯集、類型別名) 和通訊協定。

基本

  • 簡易值類型。
  • 不得選用。

FIDL 支援下列基本類型:

  • 布林值 bool
  • 帶號整數 int8 int16 int32 int64
  • 非帶號整數 uint8 uint16 uint32 uint64
  • IEEE 754 浮點 float32 float64

數字會加上其大小 (以位元為單位)。例如,int8 是 1 個位元組。

使用

// A record which contains fields of a few primitive types.
type Sprite = struct {
    x float32;
    y float32;
    index uint32;
    color uint32;
    visible bool;
};

點數

  • 已命名的位元類型。
  • 從基礎整數類型中選擇的獨立位元值子集。
  • 不得選用。
  • 位元可以是 strictflexible
  • Bits 預設為 flexible
  • strict 位元至少須有一名成員,flexible 位元可以留空。

運算子

| 是位元的位元 OR 運算子。

使用

type InfoFeatures = strict bits : uint8 {
    /// If present, this device represents WLAN hardware
    WLAN = 0x01;
    /// If present, this device is synthetic (not backed by h/w)
    SYNTH = 0x02;
    /// If present, this device receives all messages it sends
    LOOPBACK = 0x04;
};

// Underlying type is assumed to be uint32.
type AllowableSegments = flexible bits {
    TOLL_ROADS = 0b001;
    HIGHWAYS = 0b010;
    BIKE_PATHS = 0b100;
};

const ROADS AllowableSegments = AllowableSegments.TOLL_ROADS | AllowableSegments.HIGHWAYS;

列舉

  • 正確的列舉類型。
  • 從基礎整數類型中選擇的獨立已命名值子集。
  • 不得選用。
  • 列舉可以是 strictflexible
  • 列舉預設為 flexible
  • strict 列舉必須至少包含一個成員,flexible 列舉可以留空。

宣告

每個列舉元素都必須有序數。列舉的基礎類型必須是 int8、uint8、int16、uint16、int32、uint32、int64、uint64。如果省略,基礎類型會預設為 uint32

type Beverage = flexible enum : uint8 {
    WATER = 0;
    COFFEE = 1;
    TEA = 2;
    WHISKEY = 3;
};

// Underlying type is assumed to be uint32.
type Vessel = strict enum {
    CUP = 0;
    BOWL = 1;
    TUREEN = 2;
    JUG = 3;
};

使用

列舉類型會以 ID 表示,並視需要經過評估。

// A record which contains two enum fields.
type Order = struct {
    beverage Beverage;
    vessel Vessel;
};

FIDL 方案:Enum

「列舉」是一種 FIDL 資料類型,代表固定的可能常數清單,例如撲克牌上的西裝,或使用者可從下拉式選單中選取的汽車廠牌。接著,此清單值會對應到基礎整數類型,且每個值都對應其中一個列出的成員。

在下方範例中,系統會在以下情境中加入 FIDL 列舉:列舉可能是由失敗方法呼叫造成的可能錯誤值。ReadError 列舉包含兩個成員:NOT_FOUND 可用來表示在讀取嘗試期間無法比對搜尋值,而 UNKNOWN 可為所有無法明確說明的情況視為抓取錯誤。請注意,這個列舉已標示為 flexible,方便日後與新成員一起進化。

推理

原始的唯寫鍵/值存放區現已擴充,可讀取商店外的項目。

實作

適用於 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.addreaditem;

// Aliases for the key and value. Using aliases helps increase the readability of FIDL files and
// reduces likelihood of errors due to differing constraints.
alias Key = string:128;
alias Value = vector<byte>:64000;

/// 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 Key;
    value Value;
};

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

/// An enumeration of things that may go wrong when trying to read a value out of our store.
type ReadError = flexible enum {
    UNKNOWN = 0;
    NOT_FOUND = 1;
};

/// A very basic key-value store - so basic, in fact, that one may only write to it, never read!
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;

    /// Reads an item from the store.
    flexible ReadItem(struct {
        key Key;
    }) -> (Item) error ReadError;
};

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.addreaditem.Store" },
    ],
    config: {
        write_items: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

        read_items: {
            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.addreaditem.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.addreaditem.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.addreaditem.Store",
            from: "#server",
            to: "#client",
        },

        // Route diagnostics support to all children.
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

所有語言的用戶端和伺服器實作也有所變更:

Rust

用戶端

// 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_addreaditem::{Item, StoreMarker},
    fuchsia_component::client::connect_to_protocol,
    std::{str, 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}"))?;
        match store.write_item(&Item { key: key, value: value.into_bytes() }).await? {
            Ok(_) => println!("WriteItem Success"),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // The structured config for this client contains `read_items`, a vector of strings, each of
    // which is meant to be read from the key-value store. We iterate over these keys, attempting to
    // read them in turn.
    for key in config.read_items.into_iter() {
        let res = store.read_item(key.as_str()).await;
        match res.unwrap() {
            Ok(val) => {
                println!("ReadItem Success: key: {}, value: {}", key, str::from_utf8(&val.1)?)
            }
            Err(err) => println!("ReadItem 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.

use {
    anyhow::{Context as _, Error},
    fidl_examples_keyvaluestore_addreaditem::{
        Item, ReadError, StoreRequest, StoreRequestStream, WriteError,
    },
    fuchsia_component::server::ServiceFs,
    futures::prelude::*,
    lazy_static::lazy_static,
    regex::Regex,
    std::cell::RefCell,
    std::collections::hash_map::Entry,
    std::collections::HashMap,
};

lazy_static! {
    static ref KEY_VALIDATION_REGEX: Regex =
        Regex::new(r"^[A-Za-z][A-Za-z0-9_\./]{2,62}[A-Za-z0-9]$")
            .expect("Key validation regex failed to compile");
}

/// Handler for the `WriteItem` method.
fn write_item(store: &mut HashMap<String, Vec<u8>>, attempt: Item) -> 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);
    }

    // Validate the value.
    if attempt.value.is_empty() {
        println!("Write error: INVALID_VALUE, For key: {}", attempt.key);
        return Err(WriteError::InvalidValue);
    }

    // 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) => {
            println!("Wrote value at key: {}", entry.key());
            entry.insert(attempt.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, Vec<u8>>::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::ReadItem { key, responder } => {
                    println!("ReadItem request received");

                    // Read the item from the store, returning the appropriate error if it could not be found.
                    responder
                        .send(match store.borrow().get(&key) {
                            Some(found) => {
                                println!("Read value at key: {}", key);
                                Ok((&key, found))
                            }
                            None => {
                                println!("Read error: NOT_FOUND, For key: {}", key);
                                Err(ReadError::NotFound)
                            }
                        })
                        .context("error sending reply")?;
                    println!("ReadItem 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++ (有線)

用戶端

// 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.

陣列

  • 同質元素的固定長度序列。
  • 元素可以是任何類型。
  • 不得單獨使用,可包含選用類型。

使用

陣列會標示 array<T, N>,其中 T 可以是任何 FIDL 類型 (包括陣列),N 則是指定陣列中元素數量的正整數常數運算式。

// A record which contains some arrays.
type Arrays = struct {
    // array of exactly 16 floating point numbers
    matrix array<float32, 16>;

    // array of exactly 10 arrays of 4 strings each
    form array<array<string, 4>, 10>;
};

請注意,「N」會顯示為版面配置參數,也就是說,它會影響該類型的 ABI。換句話說,變更 _N_ 參數是一種 ABI 破壞性變更。

字串

  • 代表 UTF-8 編碼文字的變數長度序列。
  • 可以是選用項目;缺少的字串與空白字串各不相同。
  • 可指定大小上限,例如 string:40 代表最多 40 位元組字串。根據預設,string 表示 string:MAX,也就是無界限。
  • 字串常值支援逸出序列 \\\"\n\r\t\u{X},其中 X 是萬國碼碼點的 1 到 6 個十六進位數字。
  • 與傳統的 C 字串不同,可能含有嵌入的 NUL 位元組。

使用

字串的表示方式如下:

  • string:必要字串 (如果沒有,就會發生驗證錯誤)
  • string:optional:選用字串
  • string:N, string:<N, optional>:分別為字串和選用字串,長度上限為 N 個位元組
// A record which contains some strings.
type Document = struct {
    // title string, maximum of 40 bytes long
    title string:40;

    // description string, may be null, no upper bound on size
    description string:optional;
};

請注意,「N」會顯示為限制條件 (會顯示在 : 後方),這代表不會影響類型的 ABI。換句話說,變更 _N_ 參數並不會導致「ABI 破壞性」變更。

字串不應用於傳遞任意二進位資料,因為繫結強制執行有效的 UTF-8。針對小型資料,請考慮使用 bytes;如為 blob,則建議使用 fuchsia.mem.Buffer。詳情請參閱「我應該使用字串還是向量?」一節。

向量

  • 同質元素的變數長度序列。
  • 可以是選用項目;缺少的向量和空白向量皆不相同。
  • 可指定大小上限,例如 vector<T>:40 代表最多 40 個元素向量。根據預設,vector<T> 表示 vector<T>:MAX,也就是無界限。
  • 布林值向量目前沒有特殊情況。每個布林值元素會照常接收一個位元組。

使用

向量標示如下:

  • vector<T>T 的必要元素向量 (如果沒有,就會發生驗證錯誤)
  • vector<T>:optional:元素類型的選用向量「T」T
  • vector<T>:Nvector<T>:<N, optional>:向量和選用向量,長度上限為 N 個元素。

T 可以是任何 FIDL 類型。

// A record which contains some vectors.
type Vectors = struct {
    // a vector of up to 10 integers
    params vector<int32>:10;

    // a vector of bytes, no upper bound on size
    blob vector<uint8>;

    // a nullable vector of up to 24 strings
    nullable_vector_of_strings vector<string>:<24, optional>;

    // a vector of nullable strings, no upper bound on size
    vector_of_nullable_strings vector<string:optional>;

    // a vector of vectors of 16-element arrays of floating point numbers
    complex vector<vector<array<float32, 16>>>;
};

帳號代碼

  • 以處理值轉移 Zircon 能力。
  • 儲存為 32 位元無正負號整數。
  • 可選用;缺少的控點已編碼為零值的控制代碼。
  • 您可以選擇將控點與一組必要的 Zircon 權利建立關聯。

使用

帳號代碼會註明:

  • zx.Handle:未指定類型的必要 Zircon 控點
  • zx.Handle:optional:選用的 Zircon 控制代碼類型
  • zx.Handle:H:必要的 H 類型 Zircon 控制代碼
  • zx.Handle:<H, optional>:選用 H 類型的 Zircon 控點
  • zx.Handle:<H, R>:必要的 H 類型 Zircon 控制代碼和權利 R
  • zx.Handle:<H, R, optional>H 類型的選用 Zircon 控點搭配權利 R

H 可以是 Zircon 支援的任何物件,例如 channelthreadvmo。如需完整清單,請參閱文法

R 可以是 Zircon 支援的任何值。權限是位元類型的值,定義於 zx FIDL 程式庫中,例如 zx.Rights.READ。在傳入和傳出路線中,系統會驗證帳號代碼是否具有正確的 Zircon 物件類型,且至少與 FIDL 中指定的權限數量相同。如果控制代碼擁有的權限超過 FIDL 中指定的數量,則呼叫 zx_handle_replace 會縮減其權利。如需範例,請參閱「處理代碼的生命週期」和「RFC-0028:處理權利」一節。

結構體、資料表和包含帳號代碼的聯集都必須以 resource 修飾符標記。

// A record which contains some handles.
type Handles = resource struct {
    // a handle of unspecified type
    h zx.Handle;

    // an optional channel
    c zx.Handle:<CHANNEL, optional>;
};

結構

  • 由一連串輸入欄位組成的記錄類型。
  • 新增或移除欄位或變更類型通常與 ABI 不相容。
  • 宣告可以使用 resource 修飾符
  • 參考檔案可能box
  • 結構體包含零個以上的成員。

宣告

type CirclePoint = struct {
    x float32;
    y float32;
};

type Color = struct {
    r float32;
    g float32;
    b float32;
};

使用

結構體會以其宣告名稱表示 (例如 Circle):

  • Circle:必要圓形
  • box<Circle>:選用的圓形,儲存在外行
type Circle = struct {
    filled bool;
    center CirclePoint; // CirclePoint will be stored in-line
    radius float32;
    color box<Color>; // Color will be stored out-of-line
    dashed bool;
};

桌子

  • 記錄類型,由一連串以序數表示的欄位組成。
  • 宣告的目的是在結構定義變更時,保有前瞻性和回溯相容性。
  • 宣告可以使用 resource 修飾符
  • 資料表不得設為選用。「遺漏值」的語意會以空白資料表表示,也就是其中所有成員都不存在,以避免處理雙選用性。
  • 資料表包含零或多個成員。

宣告

type Profile = table {
    1: locales vector<string>;
    2: calendars vector<string>;
    3: time_zones vector<string>;
};

使用

資料表會以宣告的名稱表示 (例如設定檔):

  • Profile:必要設定檔

在這裡,我們會說明 Profile 如何演變並供應溫度單位。知道 Profile 先前定義 (不含溫度單位) 的用戶端仍可將其設定檔傳送至已進行更新的伺服器,以便處理較大的欄位組合。

type TemperatureUnit = enum {
    CELSIUS = 1;
    FAHRENHEIT = 2;
};

type Profile = table {
    1: locales vector<string>;
    2: calendars vector<string>;
    3: time_zones vector<string>;
    4: temperature_unit TemperatureUnit;
};

聯盟

  • 由序道和信封組成的記錄類型。
  • 序數表示成員選取項目,信封會保留內容。
  • 部署完成後仍可修改宣告內容,同時保持 ABI 的相容性。如要瞭解來源相容性注意事項,請參閱相容性指南
  • 宣告可以使用 resource 修飾符
  • 參考資料可能為選填項目。
  • 聯集可以是 strictflexible
  • 聯集預設為 flexible
  • strict」聯集必須包含一或多個成員。沒有成員的聯集將沒有居民,因此對線型格式而言不太合理。不過,系統允許無成員的 flexible 聯集,因為仍有可能解碼無成員聯集 (包含的資料一律為「不明」)。

宣告

/// The result of an operation. A result is either a single number or an
/// [Error] value.
type Result = union {
    1: number float64;
    2: reserved;
    3: error Error;
};

使用

聯集會以其宣告名稱 (例如 Result) 和選用性來表示:

  • Result:必要結果
  • Result:optional:選用結果
type Either = strict union {
    1: left Left;
    2: right Right;
};

嚴格與彈性

FIDL 類型宣告可以具有「嚴格」或「彈性」行為:

  • 除非使用 strict 修飾符宣告位元、列舉和聯集,否則相當靈活。
  • 結構體始終嚴格行為。
  • 資料表一律會有彈性行為。

僅適用於嚴格類型,將宣告未提及的資料值序列化或還原序列化屬於「驗證錯誤」

在這個例子中:

type FlexibleEither = flexible union {
    1: left Left;
    2: right Right;
};

由於具備彈性,FlexibleEither 會更容易演進第三個變化版本。用戶端知道先前 FlexibleEither 的定義時,沒有第三個變體,仍然可以從已更新的伺服器中接收包含較多變化版本集的聯集。如果聯集屬於未知的變數,繫結可能會向使用者公開不明資料 (例如原始位元組和控制),並允許重新編碼未知的聯集 (例如支援類似 Proxy 的用途)。如要進一步瞭解提供彈性類型不明資料的互動方法,請參閱繫結參考資料

詳情請參閱 RFC-0033:處理不明欄位和嚴格程度中的討論。

價值與資源

每個 FIDL 類型都是值類型資源類型。資源類型包括:

  • 帳號代碼
  • 通訊協定端點
  • 資源類型的aliases
  • 資源類型的陣列和向量
  • resource 修飾符標示的結構體、資料表和聯集
  • 選用 (或有盒裝) 的上述任一類型的參照

所有其他類型都是值類型。

值類型不得包含資源類型。例如,不正確:

type Foo = struct { // ERROR: must be "resource struct Foo"
    h zx.Handle;
};

即使類型不包含處理常式,也可以使用 resource 修飾符標示。日後如要為類型新增控制代碼,建議您這麼做,因為新增或移除 resource 修飾符需要來源相容性注意事項。例如:

// No handles now, but we will add some in the future.
type Record = resource table {
    1: str string;
};

// "Foo" must be a resource because it contains "Record", which is a resource.
type Foo = resource struct {
    record Record;
};

詳情請參閱 RFC-0057:預設的無帳號代碼

通訊協定

  • 說明透過管道傳送訊息的可叫用方法。
  • 方法由其序數識別。編譯器的計算方式如下:
    • 取得方法完整名稱的 SHA-256 雜湊。
    • 擷取雜湊摘要的前 8 個位元組。
    • 將這些位元組解讀為一個小的端子整數
    • 將該值的上位元 (即最後位元) 設為 0。
    • 如要覆寫序數,方法可包含 @selector 屬性。如果屬性的引數是有效的 FQN,則會使用它取代上述的 FQN。否則,該識別碼必須是有效 ID,且會在建構 FQN 時取代方法名稱。
  • 每個方法宣告都會陳述其引數和結果。

    • 如果未宣告任何結果,則方法為單向:伺服器不會產生任何回應。
    • 如果已宣告結果 (即使為空白),則方法為雙向:每次叫用方法都會從伺服器產生回應。
    • 如果只宣告了結果,此方法稱為事件。然後定義來自伺服器的來路不明的訊息。
    • 雙向方法可能會宣告伺服器可傳送而非回應的錯誤類型。這個類型必須是 int32uint32 或其 enum
  • 當通訊協定的伺服器即將關閉管道側邊時,可以選擇傳送 epitaph 訊息給用戶端,指出連線中斷的情況。管道必須是透過頻道傳送的最後一則訊息。Epitaph 訊息包含 zx_status_t 類型的 32 位元 int 值。負值會保留給系統錯誤代碼。ZX_OK (0) 值表示作業成功。應用程式定義的錯誤代碼 (先前定義為所有正 zx_status_t 值) 已淘汰。如要進一步瞭解單詞,請參閱 RFC-0031:類型 Epitaphs 的拒絕資訊。如要進一步瞭解 zx_status_t,請參閱「RFC-0085:減少 zx_status_t 空間」。

宣告

type DivisionError = strict enum : uint32 {
    DIVIDE_BY_ZERO = 1;
};

protocol Calculator {
    Add(struct {
        a int32;
        b int32;
    }) -> (struct {
        sum int32;
    });
    Divide(struct {
        dividend int32;
        divisor int32;
    }) -> (struct {
        quotient int32;
        remainder int32;
    }) error DivisionError;
    Clear();
    -> OnError(struct {
        status_code uint32;
    });
};

使用

通訊協定將依名稱、管道的方向和選用性來區分:

  • client_end:Protocol:透過 FIDL 通訊協定進行通訊的管道用戶端端點
  • client_end:<Protocol, optional>:上文的選用版本
  • server_end:Protocol:透過 FIDL 通訊協定進行通訊的管道伺服器端點
  • server_end:<Protocol, optional>:上文的選用版本
// A record which contains protocol-bound channels.
type Record = resource struct {
    // client endpoint of a channel bound to the Calculator protocol
    c client_end:Calculator;

    // server endpoint of a channel bound to the Science protocol
    s server_end:Science;

    // optional client endpoint of a channel bound to the
    // RealCalculator protocol
    r client_end:<RealCalculator, optional>;
};

通訊協定組成

通訊協定可包含其他通訊協定的方法。這稱為組合,從其他通訊協定組合出一項通訊協定。

組合適用於以下情況:

  1. 您有多個通訊協定都具有相同的共同行為
  2. 或是依據不同層級的功能,向不同目標對象顯示

常見行為

在第一個案例中,多個通訊協定可能存在相同的行為。舉例來說,在圖形系統中,多個不同的通訊協定可能都共用相同的設定背景和前景顏色。您不需要為每個通訊協定定義各自的色彩設定方法,而是可以定義通用通訊協定:

protocol SceneryController {
    SetBackground(struct {
        color Color;
    });
    SetForeground(struct {
        color Color;
    });
};

讓其他通訊協定也能分享這個設定檔:

protocol Drawer {
    compose SceneryController;
    Circle(struct {
        x int32;
        y int32;
        radius int32;
    });
    Square(struct {
        x int32;
        y int32;
        diagonal int32;
    });
};

protocol Writer {
    compose SceneryController;
    Text(struct {
        x int32;
        y int32;
        message string;
    });
};

上述程式碼有 SceneryControllerDrawerWriter 三種通訊協定。 Drawer 可用來繪製圖形物件,例如指定大小的圓形和正方形。這會組合 SceneryController 通訊協定的 SetBackground()SetForeground() 方法,因為其中包含 SceneryController 通訊協定 (透過 compose 關鍵字進行)。

Writer 通訊協定用於在螢幕上寫入文字,並以相同方式包含 SceneryController 通訊協定。

現在 DrawerWriter 都包含 SetBackground()SetForeground()

與使用 DrawerWriter 指定自己的色彩設定方法相比,這種做法提供多項優點:

  • 背景和前景顏色設定方式相同 (無論是用於在螢幕上繪製圓形、正方形或加入文字)。
  • 只要將新方法新增至 DrawerWriter,無需變更其定義,只要將方法新增至 SceneryController 通訊協定即可。

最後一點尤其重要,因為我們可以藉此為現有通訊協定新增功能。例如,我們可能會為圖形系統導入 alpha 混合 (或「透明度」) 功能。擴充 SceneryController 通訊協定以便處理,如下所示:

protocol SceneryController {
    SetBackground(struct { color Color; });
    SetForeground(struct { color Color; });
    SetAlphaChannel(struct { a int; });
};

我們現已擴充 DrawerWriter,以支援 Alpha 混合。

多個樂曲

組合並非一對一關係,我們可以在指定的通訊協定中納入多個組合,而且並非所有通訊協定都是由相同的內含通訊協定組合組成。

例如,我們可以設定字型特性。 字型對我們的 Drawer 通訊協定來說並沒有任何意義,但這些字型適用於我們的 Writer 通訊協定,或許也適用於其他通訊協定。

因此,我們會定義 FontController 通訊協定:

protocol FontController {
    SetPointSize(struct {
        points int32;
    });
    SetFontName(struct {
        fontname string;
    });
    Italic(struct {
        onoff bool;
    });
    Bold(struct {
        onoff bool;
    });
    Underscore(struct {
        onoff bool;
    });
    Strikethrough(struct {
        onoff bool;
    });
};

然後使用 compose 關鍵字邀請 Writer 加入這個物件:

protocol Writer {
    compose SceneryController;
    compose FontController;
    Text(struct { x int; y int; message string; });
};

我們已使用 FontController 通訊協定的方法擴充 Writer 通訊協定,在不干擾 Drawer 通訊協定 (不需要知道字型相關資訊) 的情況下

通訊協定組成與 mixin 類似。詳情請參閱 RFC-0023:組合模型

分層

在本節開頭處,我們提到了第二次的組合,也就是向不同的目標對象呈現不同層級的功能。

在這個範例中,我們有兩個獨立實用的通訊協定,一種是用來取得目前時間和時區的 Clock 通訊協定:

protocol Clock {
    Now() -> (struct {
        time Time;
    });
    CurrentTimeZone() -> (struct {
        timezone string;
    });
};

設定時間和時區的 Horologist 通訊協定:

protocol Horologist {
    SetTime(struct {
        time Time;
    });
    SetCurrentTimeZone(struct {
        timezone string;
    });
};

我們不一定要向任何用戶端公開權限更高的 Horologist 通訊協定,但我們建議將其公開至系統時鐘元件。因此,我們會建立通訊協定 (SystemClock) 組成以下兩者:

protocol SystemClock {
    compose Clock;
    compose Horologist;
};

不明互動

通訊協定可定義在接收到系統無法識別的方法呼叫或事件時,要如何回應。發生不明序數的情況主要是,用戶端和伺服器是使用不同通訊協定版本建構時,方法可能更多或較少,不過用戶端和伺服器在同一個管道中誤用不同通訊協定時也可能會發生這種情形。

如要控管通訊協定在發生這些不明互動時的行為,方法可標示為 strictflexible,而通訊協定則可標示為 closedajaropen

方法嚴格度修飾符 (strictflexible) 可用於指定結尾端在無法識別序數時,要如何讓接收端對互動做出反應。若是單向或雙向方法,傳送端是用戶端,而傳送端的事件則是伺服器。

  • strict 表示接收端不應知道互動的錯誤,如果收到 strict 不明的互動,接收器應關閉管道。
  • flexible 表示應用程式應處理不明的互動。如果通訊協定允許進行這種類型的未知互動,序數會傳遞至未知的互動處理常式,然後決定如何回應。通訊協定允許的未知互動類型是由通訊協定修飾符決定。如果未指定嚴格程度,則預設值為 flexible

通訊協定開放性修飾符 closedajaropen 會控制接收端在無法識別 flexible 互動時的回應方式。若是單向或雙向方法,接收端是伺服器,事件的接收端則是用戶端。

  • closed 表示通訊協定不接受任何未知互動。如果收到任何不明互動,無論互動是 strict 還是 flexible,繫結都會回報錯誤和結束通訊。
    • 所有方法和事件都必須宣告為 strict
  • ajar 表示通訊協定允許未知的 flexible 單向方法和事件。任何未知的雙向方法和 strict 單向方法或事件都會導致錯誤,並導致繫結關閉管道。
    • 單向方法和事件可宣告為 strictflexible
    • 雙向方法必須宣告為 strict
  • open 表示通訊協定允許任何未知的 flexible 互動。任何未知的 strict 互動仍會導致錯誤,並導致繫結關閉管道。如未指定開放性,預設值為「Open」。
    • 所有方法和事件都可以宣告為 strictflexible

以下摘要說明每種通訊協定類型中不同方法種類可使用的嚴格程度修飾符。以開放性為預設值 open,會以斜體標示。嚴格度的預設值 flexible 會以粗體標示。

嚴格 M(); 彈性 M(); 嚴格 -> M(); 彈性 -> M(); 嚴格 M() -> (); 彈性 M() -> ();
打開 P 編譯 編譯 編譯 編譯 編譯 編譯
Ajar P 編譯 編譯 編譯 編譯 編譯 無法編譯
關合的 P 編譯 無法編譯 編譯 無法編譯 編譯 無法編譯

在通訊協定中使用修飾符的範例。

open protocol Moderator {
    flexible GetPosts() -> (Posts);
    strict ApplyModeration(struct {
        post Post;
        decision Decision;
    }) -> ();
};

ajar protocol Messenger {
    strict EnableSecureMode();
    flexible AddMessageContent(struct {
        content string;
    });
    strict SendPending() -> ();
    flexible -> OnReceiveMessage(Message);
};

請注意,只有在接收端無法識別序數,且不知道互動為何時,系統才會套用不明互動處理。這表示接收端不知道互動是否應嚴格或彈性。為了讓接收器瞭解如何處理不明互動,寄件人在訊息標頭中加入一小段內容,通知接收者是否要將互動視為嚴格或彈性。因此,互動的嚴格程度取決於傳送者嘗試呼叫的方法的嚴格程度,但通訊協定對該互動的開放程度則取決於接收器的開放性。

以下說明如何在接收端無法辨識方法時,每個開放性的通訊協定都會處理每個嚴格限制的方法或事件。

嚴格 M(); 彈性 M(); 嚴格 -> M(); 彈性 -> M(); 嚴格 M() -> (); 彈性 M() -> ();
開打 P 自動結束 可處理 自動結束 可處理 自動結束 可處理
Ajar P 自動結束 可處理 自動結束 可處理 自動結束 自動結束
關合的 P 自動結束 自動結束 自動結束 自動結束 自動結束 自動結束

與組合互動

無法在 closed 通訊協定中宣告 flexible 方法和事件,而且 ajar 通訊協定中不得宣告 flexible 雙向方法。為確保系統會在各個通訊協定組合中強制執行這類規則,通訊協定可能僅撰寫其他通訊協定,使其至少達到下列關閉狀態:

  • open:可以組合任何通訊協定。
  • ajar:可撰寫 ajarclosed 通訊協定。
  • closed:只能撰寫其他 closed 通訊協定。

疊頻效應

支援類型別名。例如:

const MAX_SIZE uint32 = 100;
alias StoryID = string:MAX_SIZE;
alias Chapters = vector<StoryID>:5;

在上述內容中,ID StoryIDstring 宣告的別名,大小上限為 MAX_SIZE。ID Chapters 是五個 StoryId 元素的向量宣告的別名。

在可以使用 StoryIDChapters 的別名定義處,可以使用 ID 和 ID。請考慮:

type Message = struct {
    baseline StoryID;
    chapters Chapters;
};

此處的 Message 結構體包含名為 baselineMAX_SIZE 位元組字串,以及最多 5 字串 (稱為 chapters) 的向量。MAX_SIZE

FIDL 方案:別名

alias 是 FIDL 宣告,會將新的名稱指派給現有類型。這麼做有幾個好處:

  • 使用 alias 可確保有別名類型所代表概念的單一可靠資料來源。
  • 可為事物命名,尤其是受限的類型。
  • 立即別名類型的不同用途可能會與同一概念的例項連結。

請注意,別名目前不會傳輸至已產生的繫結程式碼。換句話說,指派給 alias 宣告的名稱永遠不會在產生的 FIDL 程式碼中顯示為宣告名稱。

在這個範例中,為 Key 新增 alias 可讓我們避免以自訂的名稱重複,同時讓讀取器清楚知道 Item 類型的 key 值和 ReadItem 要求結構中使用的 key 是刻意用的,並非只是巧妙地相同。

推理

原始的唯寫鍵/值存放區現已擴充,可讀取商店外的項目。

實作

適用於 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.addreaditem;

// Aliases for the key and value. Using aliases helps increase the readability of FIDL files and
// reduces likelihood of errors due to differing constraints.
alias Key = string:128;
alias Value = vector<byte>:64000;

/// 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 Key;
    value Value;
};

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

/// An enumeration of things that may go wrong when trying to read a value out of our store.
type ReadError = flexible enum {
    UNKNOWN = 0;
    NOT_FOUND = 1;
};

/// A very basic key-value store - so basic, in fact, that one may only write to it, never read!
@discoverable
open protocol Store {
    /// Writes an item to the store.
    flexible WriteItem(struct {
        attempt Item;
    }) -> () error WriteError;

    /// Reads an item from the store.
    flexible ReadItem(struct {
        key Key;
    }) -> (Item) error ReadError;
};

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.addreaditem.Store" },
    ],
    config: {
        write_items: {
            type: "vector",
            max_count: 16,
            element: {
                type: "string",
                max_size: 64,
            },
        },

        read_items: {
            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.addreaditem.Store" },
    ],
    expose: [
        {
            protocol: "examples.keyvaluestore.addreaditem.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.addreaditem.Store",
            from: "#server",
            to: "#client",
        },

        // Route diagnostics support to all children.
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            from: "parent",
            to: [
                "#client",
                "#server",
            ],
        },
    ],
}

所有語言的用戶端和伺服器實作也有所變更:

Rust

用戶端

// 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_addreaditem::{Item, StoreMarker},
    fuchsia_component::client::connect_to_protocol,
    std::{str, 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}"))?;
        match store.write_item(&Item { key: key, value: value.into_bytes() }).await? {
            Ok(_) => println!("WriteItem Success"),
            Err(err) => println!("WriteItem Error: {}", err.into_primitive()),
        }
    }

    // The structured config for this client contains `read_items`, a vector of strings, each of
    // which is meant to be read from the key-value store. We iterate over these keys, attempting to
    // read them in turn.
    for key in config.read_items.into_iter() {
        let res = store.read_item(key.as_str()).await;
        match res.unwrap() {
            Ok(val) => {
                println!("ReadItem Success: key: {}, value: {}", key, str::from_utf8(&val.1)?)
            }
            Err(err) => println!("ReadItem 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.

use {
    anyhow::{Context as _, Error},
    fidl_examples_keyvaluestore_addreaditem::{
        Item, ReadError, StoreRequest, StoreRequestStream, WriteError,
    },
    fuchsia_component::server::ServiceFs,
    futures::prelude::*,
    lazy_static::lazy_static,
    regex::Regex,
    std::cell::RefCell,
    std::collections::hash_map::Entry,
    std::collections::HashMap,
};

lazy_static! {
    static ref KEY_VALIDATION_REGEX: Regex =
        Regex::new(r"^[A-Za-z][A-Za-z0-9_\./]{2,62}[A-Za-z0-9]$")
            .expect("Key validation regex failed to compile");
}

/// Handler for the `WriteItem` method.
fn write_item(store: &mut HashMap<String, Vec<u8>>, attempt: Item) -> 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);
    }

    // Validate the value.
    if attempt.value.is_empty() {
        println!("Write error: INVALID_VALUE, For key: {}", attempt.key);
        return Err(WriteError::InvalidValue);
    }

    // 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) => {
            println!("Wrote value at key: {}", entry.key());
            entry.insert(attempt.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, Vec<u8>>::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::ReadItem { key, responder } => {
                    println!("ReadItem request received");

                    // Read the item from the store, returning the appropriate error if it could not be found.
                    responder
                        .send(match store.borrow().get(&key) {
                            Some(found) => {
                                println!("Read value at key: {}", key);
                                Ok((&key, found))
                            }
                            None => {
                                println!("Read error: NOT_FOUND, For key: {}", key);
                                Err(ReadError::NotFound)
                            }
                        })
                        .context("error sending reply")?;
                    println!("ReadItem 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++ (有線)

用戶端

// 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.

內建

FIDL 提供下列內建功能:

  • 原始類型:boolint8int16int32int64uint8uint16uint32uint64float32float64
  • 其他類型:stringclient_endserver_end
  • 輸入範本:arrayvectorbox
  • 別名:byte
  • 限制:optionalMAX

下列所有內建至 fidl 程式庫。這個程式庫隨時可以使用,不需要透過 using 匯入。舉例來說,如果您宣告了名為 string 的結構,則原始字串類型可以參照 fidl.string

程式庫 zx

未內建程式庫 zx,但編譯器會以特別方式處理。可在 //zircon/vdso/zx 中定義。程式庫會使用 using zx 匯入,且最常使用 zx.Handle 類型。

內嵌版面配置

版面配置也可以內嵌,而非在 type 簡介宣告中。如果特定版面配置只使用一次,這項功能就非常實用。例如,下列 FIDL:

type Options = table {
    1: reticulate_splines bool;
};

protocol Launcher {
    GenerateTerrain(struct {
        options Options;
    });
};

可使用內嵌版面配置重新編寫:

protocol Launcher {
    GenerateTerrain(struct {
        options table {
            1: reticulate_splines bool;
        };
    });
};

使用內嵌版面配置時,FIDL 會根據版面配置使用的命名結構定義,為該版面配置保留一個保證不重複的名稱。這會產生下列保留名稱:

  • 若是用做外部版面配置成員類型的內嵌版面配置,保留名稱只會是對應成員的名稱。
    • 在上述範例中,Options 名稱保留給內嵌 table 使用。
  • 針對頂層要求/回應類型,FIDL 會依據類型使用位置,串連通訊協定名稱、方法名稱,以及 "Request""Response"
    • 在上述範例中,將 LauncherGenerateTerrainRequest 名稱保留給做為 GenerateTerrain 方法要求的結構體。
    • 請注意,"Request" 後置字串表示該類型用於啟動通訊;因此,事件類型會保留 "Request" 後置字串,而非 "Response" 後置字串。

在產生的程式碼中實際使用的名稱取決於繫結,詳情請參閱個別繫結參考資料

使用做為版面配置成員類型的內嵌版面配置時,可透過兩種方式取得不同的保留名稱:

  • 重新命名版面配置成員。
  • 使用 @generated_name 屬性覆寫預留名稱。