FIDL 语言规范

本文档是 Fuchsia 接口定义语言 (FIDL) 的规范。

如需详细了解 FIDL 的总体用途、目标和要求,请参阅概览

另请参阅修改后的 FIDL 语法 EBNF 说明

语法

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

标识符

库名称

FIDL 库名称标签用于标记 FIDL 库。它们由一个或多个以英文句点 (.) 分隔的组件组成。每个组件都必须与正则表达式 [a-z][a-z0-9]* 相匹配。换句话说:库名称组成部分必须以小写字母开头,可以包含小写字母和数字。

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

标识符

FIDL 标识符、标签声明及其成员。它们必须与正则表达式 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])? 相匹配。换句话说,标识符必须以字母开头,可以包含字母、数字和下划线,但不能以一个下划线结尾。

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

FIDL 标识符区分大小写。不过,标识符必须具有唯一的规范形式,否则 FIDL 编译器会因规范名称冲突而失败(错误代码:fi-0035)。标识符的规范形式是通过将其转换为 snake_case 来获得的。

限定标识符

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 使用以下算法来解析标识符。如果“尝试解决”步骤失败,系统会继续执行下面的下一个步骤。当“解析”步骤失败时,编译器会生成错误。

  • 如果网站不符合条件:
    1. 尝试将此问题解析为当前库中的声明。
    2. 尝试解析为内置声明,例如 bool 指的是 fidl.bool
    3. 解析为上下文位/枚举成员,例如 zx.handle:CHANNEL 中的 CHANNEL 指的是 zx.ObjType.Channel
  • 如果符合 X.Y 的条件:
    1. 尝试将 X 解析为当前库中的声明:
      1. Y 解析为 X 的成员。
    2. X 解析为库名称或别名。
      1. Y 解析为 X 中的声明。
  • 如果它被限定为 x.Y.Z,其中 x 表示一个或多个组件:
    1. 尝试将 x.Y 解析为库名称或别名:
      1. Z 解析为 x.Y 中的声明。
    2. x 解析为库名称或别名:
      1. Y 解析为 x 中的声明:
        1. Z 解析为 x.Y 的成员。

完全限定名称

FIDL 使用完全限定名称(简称“FQN”)来明确引用声明和成员。FQN 由库名称、斜杠 /、声明标识符以及可选的点 . 和成员标识符组成。例如:

  • 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.Rectfuchsia.geometry.Rect 将不起作用)。

libraryusing 声明的作用域仅限于单个文件。 FIDL 库中的每个文件都必须重新声明 library 声明以及文件所需的任何 using 声明。

FIDL 编译器不需要任何特定的目录结构,但每个 FIDL 库通常都整理在以该库命名的自有目录中。包含多个文件的库通常会有一个 overview.fidl 文件,其中仅包含 library 声明以及属性和文档注释。

库的名称可在语言绑定中用作命名空间。例如,C++ 绑定生成器会将 FIDL 库 fuchsia.ui 的声明放置在 C++ 命名空间 fuchsia_ui 内。同样,Rust 绑定生成器会生成一个名为 fidl_fuchsia_ui 的箱。

类型和类型声明

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
  • 默认情况下,位数为 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;
};

使用

枚举类型由其标识符表示,如果需要,可以进行限定。

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

FIDL 秘诀:枚举

枚举是一种 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 = 1; // Avoid 0 in errors as it may be confused with success.
    INVALID_KEY = 2;
    INVALID_VALUE = 3;
    ALREADY_EXISTS = 4;
};

/// An enumeration of things that may go wrong when trying to read a value out of our store.
type ReadError = flexible enum {
    UNKNOWN = 1; // Avoid 0 in errors as it may be confused with success.
    NOT_FOUND = 2;
};

/// 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.
        {
            dictionary: "diagnostics",
            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};
use config::Config;
use fidl_examples_keyvaluestore_addreaditem::{Item, StoreMarker};
use fuchsia_component::client::connect_to_protocol;
use 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::*,
    regex::Regex,
    std::cell::RefCell,
    std::collections::HashMap,
    std::collections::hash_map::Entry,
    std::sync::LazyLock,
};

static KEY_VALIDATION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    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.

数组

  • 同构元素的固定长度序列。
  • 元素可以是任何类型。
  • 本身不能是可选类型,但可以包含可选类型。

使用

数组表示为 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 个十六进制数字,表示 Unicode 码位。
  • 可能包含嵌入的 NUL 字节,这与传统的 C 字符串不同。

使用

字符串的表示法如下:

  • 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(适用于小数据)或 fuchsia.mem.Buffer(适用于 Blob)。如需了解详情,请参阅我应该使用字符串还是矢量?

矢量

  • 同构元素的可变长度序列。
  • 可以是可选的;缺失向量和空向量是不同的。
  • 可以指定最大大小,例如 vector<T>:40 表示最多 40 个元素的向量。默认情况下,vector<T> 表示 vector<T>:MAX,即无界。
  • 对于布尔值向量,没有特殊情况。每个布尔元素都像往常一样占用一个字节。

使用

向量的表示法如下:

  • vector<T>:必需的元素类型为 T 的矢量(如果缺少,则会发生验证错误
  • vector<T>:optional:可选的元素类型为 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>:必需的 Zircon 句柄,类型为 H,权限为 R
  • zx.Handle:<H, R, optional>:可选的 Zircon 句柄,类型为 H,权限为 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>:可选的 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 如何演变为也携带温度单位。 了解之前 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 的先前定义(不含第三个变体),那么即使服务器已更新为包含更多变体,客户端仍可以从该服务器接收联合。如果联合是未知变体,绑定可能会将其作为未知数据(即原始字节和句柄)公开给用户,并允许重新编码未知联合(例如,以支持类似代理的用例)。绑定参考文档中详细介绍了用于与灵活类型未知数据交互的方法。

如需了解详情,请参阅 RFC-0033:处理未知字段和严格性

价值与资源

每个 FIDL 类型都是值类型资源类型。资源类型包括:

  • 句柄
  • 协议端点
  • 资源类型的别名
  • 资源类型的数组和矢量
  • 标有 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。否则,它必须是有效的标识符,并且在构建 FQN 时将代替方法名称使用。
  • 每个方法声明都会说明其参数和结果。

    • 如果未声明任何结果,则该方法是单向的:服务器不会生成任何响应。
    • 如果声明了结果(即使为空),则该方法是双向的:每次调用该方法都会生成来自服务器的响应。
    • 如果仅声明了结果,则该方法称为事件。然后,它定义了来自服务器的主动发送消息。
    • 双向方法可能会声明服务器可以发送的错误类型,而不是响应。此类型必须是 int32uint32 或其 enum
  • 当协议的服务器即将关闭其通道端时,可以选择向客户端发送 墓志铭消息,以指示连接的处置情况。墓志铭必须是通过该渠道发送的最后一条消息。墓志铭消息包含一个 32 位 int 值,其类型为 zx_status_t。负值保留用于系统错误代码。值 ZX_OK (0) 表示操作成功。 应用定义的错误代码(之前定义为所有正 zx_status_t 值)已被弃用。如需详细了解墓志铭,请参阅 RFC-0031:类型化墓志铭的拒绝。如需详细了解 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;
    });
};

在上述示例中,有三种协议:SceneryControllerDrawerWriterDrawer 用于在指定位置绘制具有指定大小的圆形和方形等图形对象。 它通过 compose 关键字包含 SceneryController 协议,因此会组合 SceneryController 协议中的 SetBackground()SetForeground() 方法。

用于在显示屏上写入文本的 Writer 协议以相同的方式包含 SceneryController 协议。

现在,DrawerWriter 都包含 SetBackground()SetForeground()

与让 DrawerWriter 指定自己的颜色设置方法相比,这种方法具有以下几项优势:

  • 设置背景色和前景色时,无论用于绘制圆形、正方形还是在显示屏上放置文字,方法都是相同的。
  • 只需将新方法添加到 SceneryController 协议,即可将其添加到 DrawerWriter,而无需更改它们的定义。

最后一点尤为重要,因为这使我们能够向现有协议添加功能。例如,我们可能会在图形系统中引入 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 互动仍会导致错误,并导致绑定关闭渠道。如果未指定开放程度,则默认值为“开放”。
    • 所有方法和事件都可以声明为 strictflexible

下表总结了每种协议类型中不同类型的方法允许使用的严格程度修饰符。开放度的默认值 open,以斜体标记。严格程度 flexible 的默认值以粗体标记。

strict M(); flexible M(); strict -> M(); flexible -> M(); strict M() -> (); flexible M() -> ();
打开 P 编译 编译 编译 编译 编译 编译
ajar P 编译 编译 编译 编译 编译 无法编译
closed 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);
};

请注意,只有在接收端无法识别序数且不知道互动是什么时,才会应用未知互动处理。这意味着接收端不知道互动应该是严格的还是灵活的。为了让接收者知道如何处理未知互动,发送者会在消息标头中包含一个位,用于告知接收者是将互动视为严格还是灵活。因此,互动中使用的严格程度取决于发送者尝试调用的方法的严格程度,但相应互动的协议开放程度取决于接收者的开放程度。

下表显示了当接收方无法识别方法时,具有不同严格程度的方法或事件如何被具有不同开放程度的协议处理。

strict M(); 灵活的 M(); strict -> M(); flexible -> M(); strict M() -> (); 灵活的 M() -> ();
打开 P 自动结束 可处理 自动结束 可处理 自动结束 可处理
ajar P 自动结束 可处理 自动结束 可处理 自动结束 自动结束
closed P 自动结束 自动结束 自动结束 自动结束 自动结束 自动结束

与乐曲互动

flexible 方法和事件无法在 closed 协议中声明,flexible 双向方法无法在 ajar 协议中声明。为确保这些规则在协议组合中得到强制执行,协议只能组合至少与其一样封闭的其他协议:

  • open:可以组合任何协议。
  • ajar:可以包含 ajarclosed 协议。
  • closed:只能组合其他 closed 协议。

走样

支持类型别名。例如:

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

在上述代码中,标识符 StoryID 是声明 string(最大大小为 MAX_SIZE)的别名。标识符 Chapters 是包含五个 StoryId 元素的向量声明的别名。

标识符 StoryIDChapters 可用于可以使用其别名定义的所有位置。请考虑以下因素:

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

在此示例中,Message 结构体包含一个长度为 MAX_SIZE 字节的字符串(名为 baseline)和一个最多包含 5 个长度为 MAX_SIZE 的字符串的向量(名为 chapters)。

FIDL 实践技巧:别名

alias 是一种 FIDL 声明,用于为现有类型分配新名称。这样做有几个好处:

  • 使用 alias 可确保别名类型所代表的概念只有一个可信来源。
  • 它提供了一种命名事物(尤其是受限类型)的方式。
  • 现在别名化的类型的不同用途可能会被关联为同一概念的实例。

请务必注意,别名目前不会传递到生成的绑定代码中。换句话说,分配给声明的名称永远不会在生成的 FIDL 代码中显示为声明名称。alias

在此示例中,为 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 = 1; // Avoid 0 in errors as it may be confused with success.
    INVALID_KEY = 2;
    INVALID_VALUE = 3;
    ALREADY_EXISTS = 4;
};

/// An enumeration of things that may go wrong when trying to read a value out of our store.
type ReadError = flexible enum {
    UNKNOWN = 1; // Avoid 0 in errors as it may be confused with success.
    NOT_FOUND = 2;
};

/// 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.
        {
            dictionary: "diagnostics",
            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};
use config::Config;
use fidl_examples_keyvaluestore_addreaditem::{Item, StoreMarker};
use fuchsia_component::client::connect_to_protocol;
use 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::*,
    regex::Regex,
    std::cell::RefCell,
    std::collections::HashMap,
    std::collections::hash_map::Entry,
    std::sync::LazyLock,
};

static KEY_VALIDATION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    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.

内置

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 属性替换预留名称。