This document is a specification of the Fuchsia Interface Definition Language (FIDL).
For more information about FIDL's overall purpose, goals, and requirements, see Overview.
Also, see a modified EBNF description of the FIDL grammar.
Syntax
FIDL provides a syntax for declaring data types and protocols. These declarations are collected into libraries for distribution.
FIDL declarations are stored in UTF-8 text files. Each file contains a sequence of semicolon-delimited declarations. The order of declarations within a FIDL file, or among FIDL files within a library, is irrelevant.
Comments
FIDL comments start with two forward slashes (//
) and continue to the end of
the line. Comments that start with three forward slashes (///
) are called
documentation comments, and get emitted as comments in the generated bindings.
// 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!
Keywords
The following words have special meaning in 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.
However, FIDL has no reserved keywords. For example:
// 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;
};
Identifiers
Library names
FIDL library names label FIDL libraries. They consist of one or
more components separated by dots (.
). Each component must match the regex
[a-z][a-z0-9]*
. In words: library name components must start with a lowercase
letter, can contain lowercase letters, and numbers.
// A library named "foo".
library foo;
Identifiers
FIDL identifiers label declarations and their members. They must match the
regex [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])?
. In words: identifiers must start
with a letter, can contain letters, numbers, and underscores, but cannot end
with an underscore.
// A struct named "Foo".
type Foo = struct {};
FIDL identifiers are case sensitive. However, identifiers must have unique
canonical forms, otherwise the FIDL compiler will fail with fi-0035:
Canonical name collision. The
canonical form of an identifier is obtained by converting it to snake_case
.
Qualified identifiers
FIDL always looks for unqualified symbols within the scope of the current library. To reference symbols in other libraries, you must qualify them with the library name or alias.
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;
};
Resolution algorithm
FIDL uses the following algorithm to resolve identifiers. When a "try resolving" step fails, it proceeds to the next step below. When a "resolve" step fails, the compiler produces an error.
- If it is unqualified:
- Try resolving as a declaration in the current library.
- Try resoving as a builtin declaration, e.g.
bool
refers tofidl.bool
. - Resolve as a contextual bits/enum member, e.g. the
CHANNEL
inzx.handle:CHANNEL
refers tozx.ObjType.Channel
.
- If it is qualified as
X.Y
:- Try resolving
X
as a declaration within the current library:- Resolve
Y
as a member ofX
.
- Resolve
- Resolve
X
as a library name or alias.- Resolve
Y
as a declaration inX
.
- Resolve
- Try resolving
- If it is qualified as
x.Y.Z
wherex
represents one or more components:- Try resolving
x.Y
as a library name or alias:- Resolve
Z
as a declaration inx.Y
.
- Resolve
- Resolve
x
as a library name or alias:- Resolve
Y
as a declaration inx
:- Resolve
Z
as a member ofx.Y
.
- Resolve
- Resolve
- Try resolving
Fully qualified names
FIDL uses fully qualified names (abbreviated "FQN") to refer unambiguously to
declarations and members. An FQN consts of a library name, a slash /
, a
declaration identifier, and optionally a dot .
and member identifier. For
example:
fuchsia.io/MAX_BUF
refers to theMAX_BUF
constant in libraryfuchsia.io
.fuchsia.process/Launcher.Launch
refers to theLaunch
method in theLauncher
protocol of libraryfuchsia.process
.
FQNs are used in error messages, the FIDL JSON intermediate representation, method selectors, and documentation comment cross references.
Literals
FIDL supports the following kinds of literals:
- Boolean:
true
,false
- Integer: decimal (
123
), hex (0xA1B2
), octal (0755
), binary (0b101
).- Only decimal literals can be negative.
- Floating point:
1.23
,-0.01
,1e5
,2.0e-3
- Only
e
ande-
are allowed, note+
.
- Only
- String:
"hello"
,"\\ \" \n \r \t \u{1f642}"
All letters in numeric literals (e.g. hex digits) are case insensitive.
Constants
FIDL allows defining constants for all types that support literals (boolean, integer, floating point, and string), and bits and enums. For example:
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;
Constant expressions are either literals, references to other constants,
references to bits or enum members, or a combination of bits members separated
by the pipe character (|
). FIDL does not support any other arithmetic
expressions such as 1 + 2
.
Declaration separator
FIDL uses the semicolon (;
) to separate adjacent declarations within the
file, much like C.
Libraries
Libraries are named containers of FIDL declarations.
// 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;
Libraries import other libraries with using
declarations. You can refer to
symbols in an imported library by qualifying them with the library name, as in
fuchsia.mem.Range
. With the using ... as
syntax you must qualify symbols
with the alias, as in geo.Rect
(fuchsia.geometry.Rect
would not work).
The scope of library
and using
declarations is limited to a single file.
Each file in a FIDL library must restate the library
declaration and any
using
declarations that the file needs.
The FIDL compiler does not require any particular directory structure, but each
FIDL library is usually organized in its own directory named after the library.
Libraries with more than one file conventionally have an overview.fidl
file containing only a library
declaration along with
attributes and a documentation comment.
The library's name may be used in language bindings as a namespace. For example,
the C++ bindings generator places declarations for the FIDL library fuchsia.ui
within the C++ namespace fuchsia_ui
. Similarly, the Rust bindings generator
would generate a crate named fidl_fuchsia_ui
.
Types and type declarations
FIDL supports a number of builtin types as well as declarations of new types (e.g. structs, unions, type aliases) and protocols.
Primitives
- Simple value types.
- Cannot be optional.
FIDL supports the following primitive types:
- Boolean
bool
- Signed integer
int8 int16 int32 int64
- Unsigned integer
uint8 uint16 uint32 uint64
- IEEE 754 Floating-point
float32 float64
Numbers are suffixed with their size in bits. For example, int8
is 1 byte.
Use
// A record which contains fields of a few primitive types.
type Sprite = struct {
x float32;
y float32;
index uint32;
color uint32;
visible bool;
};
Bits
- Named bit types.
- Discrete subset of bit values chosen from an underlying integer type.
- Cannot be optional.
- Bits can either be
strict
orflexible
. - Bits default to
flexible
. strict
bits must have at least one member,flexible
bits can be empty.
Operators
|
is the bitwise OR operator for bits.
Use
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;
Enums
- Proper enumerated types.
- Discrete subset of named values chosen from an underlying integer type.
- Cannot be optional.
- Enums can be
strict
orflexible
. - Enums default to
flexible
. strict
enums must have at least one member,flexible
enums can be empty.
Declaration
The ordinal is required for each enum element. The underlying type of an enum must be one of: int8, uint8, int16, uint16, int32, uint32, int64, uint64. If omitted, the underlying type defaults to 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;
};
Use
Enum types are denoted by their identifier, which may be qualified if needed.
// A record which contains two enum fields.
type Order = struct {
beverage Beverage;
vessel Vessel;
};
FIDL recipe: Enum
An enum is a FIDL data type that represents a fixed list of possible constants, like the suits in a deck of playing cards, or the make of car a user may select from a dropdown menu. This list of values is then mapped over an underlying integer type, with each value thereof corresponding to one of the listed members.
In the example below, a FIDL enum is added in a scenario where enums are a
perfect fit: enumerating the possible error values that may be emitted by a
failed method call. The ReadError
enum has two members: NOT_FOUND
is used to
indicate that a search key could not be matched during a read attempt, while
UNKNOWN
serves as a grab-bag error for all cases that cannot be explicitly
described. Note that this enum is marked flexible
, allowing it to be easily
evolved with new members in the future.
Reasoning
The original write-only key-value store is now extended with the ability to read items back out of the store.
Implementation
The changes applied to the FIDL and CML definitions are as follows:
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
Client
// 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, }, }, }, }
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. { 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", }, ], }
Realm
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { children: [ { name: "client", url: "#meta/client.cm", }, { name: "server", url: "#meta/server.cm", }, ], offer: [ // Route the protocol under test from the server to the client. { protocol: "examples.keyvaluestore.addreaditem.Store", from: "#server", to: "#client", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], from: "parent", to: [ "#client", "#server", ], }, ], }
Client and server implementations for all languages change as well:
Rust
Client
// 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(()) }
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}, 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++ (Natural)
Client
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
Server
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
C++ (Wire)
Client
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
Server
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
HLCPP
Client
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
Server
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
Arrays
- Fixed-length sequences of homogeneous elements.
- Elements can be of any type.
- Cannot be optional themselves; may contain optional types.
Use
Arrays are denoted array<T, N>
where T can be any FIDL type (including
an array) and N is a positive integer constant expression that specifies the
number of elements in the array.
// 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>;
};
Note that N appears as a layout parameter, which means that it affects the ABI
of the type. In other words, changing the parameter _N_
is an
ABI-breaking change.
Strings
- Variable-length sequence of bytes representing text in UTF-8 encoding.
- Can be optional; absent strings and empty strings are distinct.
- Can specify a maximum size, e.g.
string:40
for a maximum 40 byte string. By default,string
meansstring:MAX
, i.e unbounded. - String literals support the escape sequences
\\
,\"
,\n
,\r
,\t
, and\u{X}
where theX
is 1 to 6 hex digits for a Unicode code point. - May contain embedded
NUL
bytes, unlike traditional C strings.
Use
Strings are denoted as follows:
string
: required string (validation error occurs if absent)string:optional
: optional stringstring:N, string:<N, optional>
: string, and optional string, respectively, with maximum length of N bytes
// 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;
};
Note that N appears as a constraint (it appears after the :
), which means
that it does not affect the ABI of the type. In other words, changing the
parameter _N_
is not an ABI-breaking change.
Strings should not be used to pass arbitrary binary data since bindings enforce valid UTF-8. Instead, consider
bytes
for small data orfuchsia.mem.Buffer
for blobs. See Should I use string or vector? for details.
Vectors
- Variable-length sequence of homogeneous elements.
- Can be optional; absent vectors and empty vectors are distinct.
- Can specify a maximum size, e.g.
vector<T>:40
for a maximum 40 element vector. By default,vector<T>
meansvector<T>:MAX
, i.e. unbounded. - There is no special case for vectors of bools. Each bool element takes one byte as usual.
Use
Vectors are denoted as follows:
vector<T>
: required vector of element type T (validation error occurs if absent)vector<T>:optional
: optional vector of element type Tvector<T>:N
andvector<T>:<N, optional>
: vector, and optional vector, respectively, with maximum length of N elements
T can be any FIDL type.
// 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>>>;
};
Handles
- Transfers a Zircon capability by handle value.
- Stored as a 32-bit unsigned integer.
- Can be optional; absent handles are encoded as a zero-valued handle.
- Handles may optionally be associated with a type and set of required Zircon rights.
Use
Handles are denoted:
zx.Handle
: required Zircon handle of unspecified typezx.Handle:optional
: optional Zircon handle of unspecified typezx.Handle:H
: required Zircon handle of type Hzx.Handle:<H, optional>
: optional Zircon handle of type Hzx.Handle:<H, R>
: required Zircon handle of type H with rights Rzx.Handle:<H, R, optional>
: optional Zircon handle of type H with rights R
H can be any object supported by
Zircon, e.g. channel
, thread
, vmo
. Please refer to the
grammar for a full list.
R can be any right supported by Zircon.
Rights are bits-typed values, defined in the zx
FIDL library, e.g. zx.Rights.READ
. In both the incoming and outgoing
directions, handles are validated to have the correct Zircon object type and at
least as many rights as are specified in FIDL. If the handle has more rights
than is specified in FIDL, then its rights will be reduced by a call to
zx_handle_replace
. See Life of a handle for an example and RFC-0028: Handle
rights for further
details.
Structs, tables, and unions containing handles must be marked with the
resource
modifier.
// 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>;
};
Structs
- Record type consisting of a sequence of typed fields.
- Adding or removing fields or changing their types is generally not ABI compatible.
- Declaration can have the
resource
modifier. - References may be
box
ed. - Structs contain zero or more members.
Declaration
type CirclePoint = struct {
x float32;
y float32;
};
type Color = struct {
r float32;
g float32;
b float32;
};
Use
Structs are denoted by their declared name (e.g. Circle):
Circle
: required Circlebox<Circle>
: optional Circle, stored out-of-line.
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;
};
Tables
- Record type consisting of a sequence of typed fields with ordinals.
- Declaration is intended for forward and backward compatibility in the face of schema changes.
- Declaration can have the
resource
modifier. - Tables cannot be optional. The semantics of "missing value" is expressed by an empty table i.e. where all members are absent, to avoid dealing with double optionality.
- Tables contain zero or more members.
Declaration
type Profile = table {
1: locales vector<string>;
2: calendars vector<string>;
3: time_zones vector<string>;
};
Use
Tables are denoted by their declared name (e.g. Profile):
Profile
: required Profile
Here, we show how Profile
evolves to also carry temperature units.
A client aware of the previous definition of Profile
(without temperature units)
can still send its profile to a server that has been updated to handle the larger
set of fields.
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;
};
Unions
- Record type consisting of an ordinal and an envelope.
- Ordinal indicates member selection, envelope holds contents.
- Declaration can be modified after deployment, while maintaining ABI compatibility. See the Compatibility Guide for source-compatibility considerations.
- Declaration can have the
resource
modifier. - Reference may be optional.
- Unions can either be
strict
orflexible
. - Unions default to
flexible
. strict
unions must contain one or more members. A union with no members would have no inhabitants and thus would make little sense in a wire format. However, memberlessflexible
unions are allowed, as it is still possible to decode an memberless union (the contained data is always "unknown").
Declaration
/// 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;
};
Use
Unions are denoted by their declared name (e.g. Result) and optionality:
Result
: required ResultResult:optional
: optional Result
type Either = strict union {
1: left Left;
2: right Right;
};
Strict vs. flexible
FIDL type declarations can either have strict or flexible behavior:
- Bits, enums, and unions are flexible unless declared with the
strict
modifier. - Structs always have strict behavior.
- Tables always have flexible behavior.
For strict types only, serializing or deserializing a value that contains data not described in the declaration is a validation error.
In this example:
type FlexibleEither = flexible union {
1: left Left;
2: right Right;
};
By virtue of being flexible, it is simpler for FlexibleEither
to evolve to
carry a third variant. A client aware of the previous definition of
FlexibleEither
without the third variant can still receive a union from a
server that has been updated to contain the larger set of variants. If the
union is of the unknown variant, bindings may expose it as unknown data (i.e. as
raw bytes and handles) to the user and allow re-encoding the unknown union (e.g.
to support proxy-like use cases). The methods provided for interacting with
unknown data for flexible types are described in detail in the bindings
reference.
More details are discussed in RFC-0033: Handling of Unknown Fields and Strictness.
Value vs. resource
Every FIDL type is either a value type or a resource type. Resource types include:
- handles
- protocol endpoints
- aliases of resource types
- arrays and vectors of resource types
- structs, tables, and unions marked with the
resource
modifier - optional (or boxed) references to any of the above types
All other types are value types.
Value types must not contain resource types. For example, this is incorrect:
type Foo = struct { // ERROR: must be "resource struct Foo"
h zx.Handle;
};
Types can be marked with the resource
modifier even if they do not contain
handles. You should do this if you intend to add handles to the type in the
future, since adding or removing the resource
modifier requires
source-compatibility considerations. For example:
// 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;
};
More details are discussed in RFC-0057: Default No Handles.
Protocols
- Describe methods that can be invoked by sending messages over a channel.
- Methods are identified by their ordinal. The compiler calculates it by:
- Taking the SHA-256 hash of the method's fully qualified name.
- Extracting the first 8 bytes of the hash digest,
- Interpreting those bytes as a little endian integer,
- Setting the upper bit (i.e. last bit) of that value to 0.
- To override the ordinal, methods can have a
@selector
attribute. If the attribute's argument is a valid FQN, it will be used in place of the FQN above. Otherwise, it must be a valid identifier, and will be used in place of the method name when constructing the FQN.
Each method declaration states its arguments and results.
- If no results are declared, then the method is one-way: no response will be generated by the server.
- If results are declared (even if empty), then the method is two-way: each invocation of the method generates a response from the server.
- If only results are declared, the method is referred to as an event. It then defines an unsolicited message from the server.
- Two-way methods may declare an error type that a server can send
instead of the response. This type must be an
int32
,uint32
, or anenum
thereof.
When a server of a protocol is about to close its side of the channel, it may elect to send an epitaph message to the client to indicate the disposition of the connection. The epitaph must be the last message delivered through the channel. An epitaph message includes a 32-bit int value of type zx_status_t. Negative values are reserved for system error codes. The value
ZX_OK
(0) indicates an operation was successful. Application-defined error codes (previously defined as all positivezx_status_t
values) are deprecated. For more details about epitaphs, see rejection of RFC-0031: Typed Epitaphs. For more details aboutzx_status_t
see RFC-0085: Reducing the zx_status_t space.
Declaration
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;
});
};
Use
Protocols are denoted by their name, directionality of the channel, and optionality:
client_end:Protocol
: client endpoint of channel communicating over the FIDL protocolclient_end:<Protocol, optional>
: optional version of the aboveserver_end:Protocol
: server endpoint of a channel communicating over the FIDL protocolserver_end:<Protocol, optional>
: optional version of the above
// 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>;
};
Protocol composition
A protocol can include methods from other protocols. This is called composition: you compose one protocol from other protocols.
Composition is used in the following cases:
- you have multiple protocols that all share some common behavior(s)
- you have varying levels of functionality you want to expose to different audiences
Common behavior
In the first case, there might be behavior that's shared across multiple protocols. For example, in a graphics system, several different protocols might all share a common need to set a background and foreground color. Rather than have each protocol define their own color setting methods, a common protocol can be defined:
protocol SceneryController {
SetBackground(struct {
color Color;
});
SetForeground(struct {
color Color;
});
};
It can then be shared by other protocols:
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;
});
};
In the above, there are three protocols, SceneryController
, Drawer
, and Writer
.
Drawer
is used to draw graphical objects, like circles and squares at given locations
with given sizes.
It composes the methods SetBackground() and SetForeground() from
the SceneryController
protocol because it includes the SceneryController
protocol
(by way of the compose
keyword).
The Writer
protocol, used to write text on the display, includes the SceneryController
protocol in the same way.
Now both Drawer
and Writer
include SetBackground() and SetForeground().
This offers several advantages over having Drawer
and Writer
specify their own color
setting methods:
- the way to set background and foreground colors is the same, whether it's used to draw a circle, square, or put text on the display.
- new methods can be added to
Drawer
andWriter
without having to change their definitions, simply by adding them to theSceneryController
protocol.
The last point is particularly important, because it allows us to add functionality
to existing protocols.
For example, we might introduce an alpha-blending (or "transparency") feature to
our graphics system.
By extending the SceneryController
protocol to deal with it, perhaps like so:
protocol SceneryController {
SetBackground(struct { color Color; });
SetForeground(struct { color Color; });
SetAlphaChannel(struct { a int; });
};
we've now extended both Drawer
and Writer
to be able to support alpha blending.
Multiple compositions
Composition is not a one-to-one relationship — we can include multiple compositions into a given protocol, and not all protocols need be composed of the same mix of included protocols.
For example, we might have the ability to set font characteristics.
Fonts don't make sense for our Drawer
protocol, but they do make sense for our Writer
protocol, and perhaps other protocols.
So, we define our FontController
protocol:
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;
});
};
and then invite Writer
to include it, by using the compose
keyword:
protocol Writer {
compose SceneryController;
compose FontController;
Text(struct { x int; y int; message string; });
};
Here, we've extended the Writer
protocol with the FontController
protocol's methods,
without disturbing the Drawer
protocol (which doesn't need to know anything about fonts).
Protocol composition is similar to mixin. More details are discussed in RFC-0023: Compositional Model.
Layering
At the beginning of this section, we mentioned a second use for composition, namely exposing various levels of functionality to different audiences.
In this example, we have two protocols that are independently useful, a Clock
protocol
to get the current time and timezone:
protocol Clock {
Now() -> (struct {
time Time;
});
CurrentTimeZone() -> (struct {
timezone string;
});
};
And an Horologist
protocol that sets the time and timezone:
protocol Horologist {
SetTime(struct {
time Time;
});
SetCurrentTimeZone(struct {
timezone string;
});
};
We may not necessarily wish to expose the more privileged Horologist
protocol to just
any client, but we do want to expose it to the system clock component.
So, we create a protocol (SystemClock
) that composes both:
protocol SystemClock {
compose Clock;
compose Horologist;
};
Unknown interactions
Protocols can define how they react when they receive a method call or event which has an ordinal which isn't recognized. Unrecognized ordinals occur primarily when a client and server were built using different versions of a protocol which may have more or fewer methods, though it can also occur if client and server are mistakenly using different protocols on the same channel.
To control the behavior of the protocol when these unknown interactions occur,
methods can be marked as either strict
or flexible
, and protocols can be
marked as closed
, ajar
, or open
.
The method strictness modifiers, strict
and flexible
, specify how the
sending end would like the receiving end to react to the interaction if it does
not recognize the ordinal. For a one-way or two-way method, the sending end is
the client, and for an event the sending end is the server.
strict
means that it should be an error for the receiving end not to know the interaction. If astrict
unknown interaction is received, the receiver should close the channel.flexible
means that the unknown interaction should be handled by the application. If the protocol allows for that type of unknown interaction, the ordinal is passed to an unknown interaction handler which can then decide how to react to it. What types of unknown interactions a protocol allows is determined by the protocol modifier.flexible
is the default value if no strictness is specified.
The protocol openness modifiers, closed
, ajar
and open
control how the
receiving end reacts to flexible
interactions if it does not recognize the
ordinal. For a one-way or two-way method, the receiving end is the server, and
for an event, the receiving end is the client.
closed
means the protocol does not accept any unknown interactions. If any unknown interaction is received, the bindings report an error and end communication, regardless of whether the interaction isstrict
orflexible
.- All methods and events must be declared as
strict
.
- All methods and events must be declared as
ajar
means that the protocol allows unknownflexible
one-way methods and events. Any unknown two-way methods andstrict
one-way methods or events still cause an error and result in the bindings closing the channel.- One-way methods and events may be declared as either
strict
orflexible
. - Two-way methods must be declared as
strict
.
- One-way methods and events may be declared as either
open
means that the protocol allows any unknownflexible
interactions. Any unknownstrict
interactions still cause an error and result in the bindings closing the channel. Open is the default value if no openness is specified.- All methods and events may be declared as either
strict
orflexible
.
- All methods and events may be declared as either
Here is a summary of which strictness modifiers are allowed for different kinds
of methods in each protocol type. The default value of openness, open
, marked
in italics. The default value of strictness, flexible
, is marked in
bold.
strict M(); | flexible M(); | strict -> M(); | flexible -> M(); | strict M() -> (); | flexible M() -> (); | |
---|---|---|---|---|---|---|
open P | compiles | compiles | compiles | compiles | compiles | compiles |
ajar P | compiles | compiles | compiles | compiles | compiles | fails to compile |
closed P | compiles | fails to compile | compiles | fails to compile | compiles | fails to compile |
Example usage of the modifiers on a protocol.
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);
};
Keep in mind that unknown interaction handling applies only when the receiving end doesn't recognize the ordinal and doesn't know what the interaction is. This means that the receiving end does not know whether the interaction is supposed to be strict or flexible. To allow the receiver to know how to handle an unknown interaction, the sender includes a bit in the message header which tells the receiver whether to treat the interaction as strict or flexible. Therefore, the strictness used in an interaction is based on what strictness the sender has for the method it tried to call, but the protocol's openness for that interaction depends on what openness the receiver has.
Here's how a method or event with each strictness is handled by a protocol with each openness when the receiving side doesn't recognize the method.
strict M(); | flexible M(); | strict -> M(); | flexible -> M(); | strict M() -> (); | flexible M() -> (); | |
---|---|---|---|---|---|---|
open P | auto-closed | handleable | auto-closed | handleable | auto-closed | handleable |
ajar P | auto-closed | handleable | auto-closed | handleable | auto-closed | auto-closed |
closed P | auto-closed | auto-closed | auto-closed | auto-closed | auto-closed | auto-closed |
Interaction with composition
flexible
methods and events cannot be declared in closed
protocols and
flexible
two-way methods cannot be declared in ajar
protocols. To ensure
these rules are enforced across protocol composition, a protocol may only
compose other protocols that are at least as closed as it is:
open
: Can compose any protocol.ajar
: Can composeajar
andclosed
protocols.closed
: Can only compose otherclosed
protocols.
Aliasing
Type aliasing is supported. For example:
const MAX_SIZE uint32 = 100;
alias StoryID = string:MAX_SIZE;
alias Chapters = vector<StoryID>:5;
In the above, the identifier StoryID
is an alias for the declaration of a
string
with a maximum size of MAX_SIZE
. The identifier Chapters
is an
alias for a vector declaration of five StoryId
elements.
The identifiers StoryID
and Chapters
can be used wherever their aliased
definitions can be used.
Consider:
type Message = struct {
baseline StoryID;
chapters Chapters;
};
Here, the Message
struct contains a string of MAX_SIZE
bytes called baseline
,
and a vector of up to 5
strings of MAX_SIZE
called chapters
.
FIDL recipe: Alias
An alias
is a FIDL declaration that assigns a new name to an existing type.
This has several benefits:
- Using
alias
ensures that there is a single source of truth for the concept the aliased type represents. - It provides a way to name things, especially constrained types.
- Disparate uses of the now-aliased type may be linked as being instances of the same concept.
It is important to note that aliases do not carry through to the generated
bindings code at the moment. In other words, the name assigned to an alias
declaration will never appear as a declaration name in the generated FIDL code.
In this example, adding an alias
for Key
allows us to avoid repetition with
a bespoke name, while also making clear to the reader that both the key
value
on the Item
type and the key
used in the ReadItem
request struct are
purposefully, and not merely coincidentally, the same thing.
Reasoning
The original write-only key-value store is now extended with the ability to read items back out of the store.
Implementation
The changes applied to the FIDL and CML definitions are as follows:
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
Client
// 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, }, }, }, }
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. { 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", }, ], }
Realm
// Copyright 2022 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. { children: [ { name: "client", url: "#meta/client.cm", }, { name: "server", url: "#meta/server.cm", }, ], offer: [ // Route the protocol under test from the server to the client. { protocol: "examples.keyvaluestore.addreaditem.Store", from: "#server", to: "#client", }, // Route diagnostics support to all children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], from: "parent", to: [ "#client", "#server", ], }, ], }
Client and server implementations for all languages change as well:
Rust
Client
// 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(()) }
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}, 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++ (Natural)
Client
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
Server
// TODO(https://fxbug.dev/42060656): C++ (Natural) implementation.
C++ (Wire)
Client
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
Server
// TODO(https://fxbug.dev/42060656): C++ (Wire) implementation.
HLCPP
Client
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
Server
// TODO(https://fxbug.dev/42060656): HLCPP implementation.
Builtins
FIDL provides the following builtins:
- Primitive types:
bool
,int8
,int16
,int32
,int64
,uint8
,uint16
,uint32
,uint64
,float32
,float64
. - Other types:
string
,client_end
,server_end
. - Type templates:
array
,vector
,box
. - Aliases:
byte
. - Constraints:
optional
,MAX
.
All builtins below to the fidl
library. This library is always available and
does not need to be imported with using
. For example, if you declare a struct
named string
, you can refer to the original string type as fidl.string
.
Library zx
Library zx
is not built in, but it is treated specially by the compiler. It is
defined in //zircon/vdso/zx. Libraries import it with
using zx
and most commonly use the zx.Handle
type.
Inline layouts
Layouts can also be specified inline, rather than in a type
introduction
declaration. This is useful when a specific layout is only used once. For
example, the following FIDL:
type Options = table {
1: reticulate_splines bool;
};
protocol Launcher {
GenerateTerrain(struct {
options Options;
});
};
can be rewritten using an inline layout:
protocol Launcher {
GenerateTerrain(struct {
options table {
1: reticulate_splines bool;
};
});
};
When an inline layout is used, FIDL will reserve a name for it that is guaranteed to be unique, based on the naming context that the layout is used in. This results in the following reserved names:
- For inline layouts used as the type of an outer layout member, the reserved
name is simply the name of the corresponding member.
- In the example above, the name
Options
is reserved for the inlinetable
.
- In the example above, the name
- For top level request/response types, FIDL concatenates the protocol name,
the method name, and then either
"Request"
or"Response"
depending on where the type is used.- In the example above, the name
LauncherGenerateTerrainRequest
is reserved for the struct used as the request of theGenerateTerrain
method. - Note that the
"Request"
suffix denotes that the type is used to initiate communication; for this reason, event types will have the"Request"
suffix reserved instead of the"Response"
suffix.
- In the example above, the name
The name that is actually used in the generated code depends on the binding, and is described in the individual bindings references.
For inline layouts used as the type of a layout member, there are two ways to obtain a different reserved name:
- Rename the layout member.
- Override the reserved name using the
@generated_name
attribute.