This document is a specification of the Fuchsia Interface Definition Language (FIDL) syntax.
For more information about FIDL's overall purpose, goals, and requirements, see Overview.
Also, see a modified EBNF description of the FIDL grammar.
FIDL provides a syntax for declaring named bits, constants, enums, structs, tables, unions, and protocols. These declarations are collected into libraries for distribution.
FIDL declarations are stored in plain text UTF-8 files. Each file consists of a sequence of semicolon-delimited declarations. The order of declarations within a FIDL file, or among FIDL files within a library, is irrelevant. FIDL does not require (or support) forward declarations of any kind.
FIDL comments start with two (//
) or three (///
) forward slashes, continue to the end of the line, and can contain UTF-8 content (which is, of course, ignored). The three-forward-slash variant is a “documentation comment”, and causes the comment text to be emitted into the generated code (as a comment, escaped correctly for the target language).
// this is a comment /// and this one is too, but it also ends up in the generated code struct Foo { // plain comment int32 f; // as is this one }; // and this is the last one!
Note that documentation comments can also be provided via the [Doc]
attribute.
The following are keywords in FIDL.
as, bits, compose, const, enum, library, protocol, struct, table, union, using, xunion.
FIDL identifiers 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.
Identifiers are case-sensitive.
// a library named "foo" library foo; // a struct named "Foo" struct Foo { }; // a struct named "struct" struct struct { };
Note: While using keywords as identifiers is supported, it can lead to confusion, and should the be considered on a case-by-case basis. See the Names
section of the Style Rubric
FIDL always looks for unqualified symbols within the scope of the current library. To reference symbols in other libraries, they must be qualified by prefixing the identifier 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(Thing thing, tex.Color color); }; struct Thing { string name; };
textures.fidl:
library textures; struct Color { uint32 rgba; };
FIDL supports integer, floating point, boolean, string, and enumeration literals, using a simplified syntax familiar to C programmers (see below for examples).
FIDL supports the following constant types: bits, booleans, signed and unsigned integers, floating point values, strings, and enumerations. The syntax is similar to C:
const bool ENABLED_FLAG = true; const int8 OFFSET = -33; const uint16 ANSWER = 42; const uint16 ANSWER_IN_BINARY = 0b101010; const uint32 POPULATION_USA_2018 = 330000000; const uint64 DIAMOND = 0x183c7effff7e3c18; const uint64 FUCHSIA = 4054509061583223046; const string USERNAME = "squeenze"; const float32 MIN_TEMP = -273.15; const float64 CONVERSION_FACTOR = 1.41421358; const Beverage MY_DRINK = Beverage.WATER;
These declarations introduce a name within their scope. The constant's type must be either a primitive or an enum.
Constant expressions are either literals or the names of other constant expressions.
For greater clarity, there is no expression processing in FIDL; that is, you cannot declare a constant as having the value
6 + 5
, for example.
Primitive structure members may have initialization values specified in the declaration. For example:
struct Color { uint32 background_rgb = 0xFF77FF; // fuchsia is the default background uint32 foreground_rgb; // there is no default foreground color };
If the programmer does not supply a background color, the default value of 0xFF77FF
will be used.
However, if the program does not supply a foreground color, there is no default. The foreground color must be supplied; otherwise it‘s a logic error on the programmer’s part.
There is a subtlety about the semantics and what defaults mean:
FIDL uses the semi-colon ‘;’ to separate adjacent declarations within the file, much like C.
Libraries are named containers of FIDL declarations.
Each library has a name consisting of a single identifier (e.g., “objects”), or multiple identifiers separated by dots (e.g., “fuchsia.composition”). Library names are used in Qualified Identifiers.
// library identifier separated by dots library fuchsia.composition; // "using" to import library "fuchsia.buffers" using fuchsia.buffers; // "using" to import library "fuchsia.geometry" and create a shortform called "geo" using fuchsia.geometry as geo;
Libraries may declare that they use other libraries with a “using” declaration. This allows the library to refer to symbols defined in other libraries upon which they depend. Symbols which are imported this way may be accessed by:
In the source tree, each library consists of a directory with some number of .fidl files. The name of the directory is irrelevant to the FIDL compiler but by convention it should resemble the library name itself. A directory should not contain FIDL files for more than one library.
The scope of “library” and “using” declarations is limited to a single file. Each individual file within a FIDL library must restate the “library” declaration together with any “using” declarations needed by that file.
The library's name may be used by certain language bindings to provide scoping for symbols emitted by the code generator.
For example, the C++ bindings generator places declarations for the FIDL library “fuchsia.ui” within the C++ namespace “fuchsia::ui”. Similarly, for languages such as Dart and Rust which have their own module system, each FIDL library is compiled as a module for that language.
The following primitive types are supported:
bool
int8 int16 int32 int64
uint8 uint16 uint32 uint64
float32 float64
Numbers are suffixed with their size in bits, bool
is 1 byte.
We also alias byte
to mean uint8
as a built-in alias.
// A record which contains fields of a few primitive types. struct Sprite { float32 x; float32 y; uint32 index; uint32 color; bool visible; };
// Bit definitions for Info.features field bits InfoFeatures : uint32 { WLAN = 0x00000001; // If present, this device represents WLAN hardware SYNTH = 0x00000002; // If present, this device is synthetic (not backed by h/w) LOOPBACK = 0x00000004; // If present, this device receives all messages it sends };
The ordinal index 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 is assumed to be uint32.
// An enum declared at library scope. enum Beverage : uint8 { WATER = 0; COFFEE = 1; TEA = 2; WHISKEY = 3; }; // An enum declared at library scope. // Underlying type is assumed to be uint32. enum Vessel { CUP = 0; BOWL = 1; TUREEN = 2; JUG = 3; };
Enum types are denoted by their identifier, which may be qualified if needed.
// A record which contains two enum fields. struct Order { Beverage beverage; Vessel vessel; };
Arrays are denoted array<T>:n
where T can be any FIDL type (including an array) and n is a positive integer constant expression which specifies the number of elements in the array.
// A record which contains some arrays. struct Record { // array of exactly 16 floating point numbers array<float32>:16 matrix; // array of exactly 10 arrays of 4 strings each array<array<string>:4>:10 form; };
string:40
for a maximum 40 byte string.NUL
bytes, unlike traditional C strings.Strings are denoted as follows:
string
: non-nullable string (validation error occurs if null is encountered)string?
: nullable stringstring:N, string:N?
: string, and nullable string, respectively, with maximum length of N bytes// A record which contains some strings. struct Record { // title string, maximum of 40 bytes long string:40 title; // description string, may be null, no upper bound on size string? description; };
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.
vector<T>:40
for a maximum 40 element vector.bytes
to mean vector<uint8>
, and it can be size bound in a similar fashion e.g. bytes:1024
.Vectors are denoted as follows:
vector<T>
: non-nullable vector of element type T (validation error occurs if null is encountered)vector<T>?
: nullable vector of element type Tvector<T>:N, vector<T>:N?
: vector, and nullable vector, respectively, with maximum length of N elementsT can be any FIDL type.
// A record which contains some vectors. struct Record { // a vector of up to 10 integers vector<int32>:10 params; // a vector of bytes, no upper bound on size bytes blob; // a nullable vector of up to 24 strings vector<string>:24? nullable_vector_of_strings; // a vector of nullable strings, no upper bound on size vector<string?> vector_of_nullable_strings; // a vector of vectors of 16-element arrays of floating point numbers vector<vector<array<float32>:16>> complex; };
Handles are denoted:
handle
: non-nullable Zircon handle of unspecified typehandle?
: nullable Zircon handle of unspecified typehandle<H>
: non-nullable Zircon handle of type Hhandle<H>?
: nullable Zircon handle of type HH can be any object supported by Zircon, e.g. channel
, thread
, vmo
. Please refer to the grammar for a full list.
// A record which contains some handles. struct Record { // a handle of unspecified type handle h; // an optional channel handle<channel>? c; };
struct Point { float32 x; float32 y; }; struct Color { float32 r; float32 g; float32 b; };
Structs are denoted by their declared name (eg. Circle) and nullability:
Circle
: non-nullable CircleCircle?
: nullable Circlestruct Circle { bool filled; Point center; // Point will be stored in-line float32 radius; Color? color; // Color will be stored out-of-line bool dashed; };
table Profile { 1: vector<string> locales; 2: vector<string> calendars; 3: vector<string> time_zones; };
Tables are denoted by their declared name (eg. Profile):
Profile
: non-nullable ProfileHere, 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 which has been updated to handle the larger set of fields.
enum TemperatureUnit { CELSIUS = 1; FAHRENHEIT = 2; }; table Profile { 1: vector<string> locales; 2: vector<string> calendars; 3: vector<string> time_zones; 4: TemperatureUnit temperature_unit; };
{%includecode gerrit_repo="fuchsia/samples" gerrit_path="src/calculator/fidl/calculator.fidl" region_tag="union" %}
Unions are denoted by their declared name (e.g. Result) and nullability:
Result
: non-nullable ResultResult?
: nullable ResultUnions can also be declared as strict or flexible. If neither strict nor flexible is specified, the union is considered to be strict.
strict union StrictEither { 1: Left left; 2: Right right; }; union ImplicitlyStrictEither { 1: Left left; 2: Right right; } flexible union FlexibleEither { 1: Left left; 2: Right right; };
Seralizing or deserializing a union from a value with an ordinal that is not defined is a validation error for strict unions, but is allowed and exposed to the user as unknown data for flexible unions. In the above example, it is possible 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 which has been updated to contain the larger set of variants. If the union is of the unknown variant, the data is exposed as unknown data by the bindings.
Describe methods which can be invoked by sending messages over a channel.
Methods are identified by their ordinal index. The compiler calculates the ordinal by
Selector
attribute. The value of the Selector
attribute will be used in the place of the method name above.Each method declaration states its arguments and results.
int32
, uint32
, or an enum
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. Positive values are reserved for application errors. A status of ZX_OK indicates successful operation.
enum DivisionError : uint32 { DivideByZero = 1; }; protocol Calculator { Add(int32 a, int32 b) -> (int32 sum); Divide(int32 dividend, int32 divisor) -> (int32 quotient, int32 remainder) error DivisionError; Clear(); -> OnClear(); };
Protocols are denoted by their name, directionality of the channel, and optionality:
Protocol
: non-nullable FIDL protocol (client endpoint of channel)Protocol?
: nullable FIDL protocol (client endpoint of channel)request<Protocol>
: non-nullable FIDL protocol request (server endpoint of channel)request<Protocol>?
: nullable FIDL protocol request (server endpoint of channel)// A record which contains protocol-bound channels. struct Record { // client endpoint of a channel bound to the Calculator protocol Calculator c; // server endpoint of a channel bound to the Science protocol request<Science> s; // optional client endpoint of a channel bound to the // RealCalculator protocol RealCalculator? r; };
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:
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:
struct Color { int16 r; int16 g; int16 b; } protocol SceneryController { SetBackground(Color color); SetForeground(Color color); };
It can then be shared by other protocols:
protocol Drawer { compose SceneryController; Circle(int x, int y, int radius); Square(int x, int y, int diagonal); }; protocol Writer { compose SceneryController; Text(int x, int y, string message); };
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:
Drawer
and Writer
without having to change their definitions, simply by adding them to the SceneryController
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(Color color); SetForeground(Color color); SetAlphaChannel(int a); };
we've now extended both Drawer
and Writer
to be able to support alpha blending.
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(int points); SetFontName(string fontname); Italic(bool onoff); Bold(bool onoff); Underscore(bool onoff); Strikethrough(bool onoff); };
and then invite Writer
to include it, by using the compose
keyword:
protocol Writer { compose SceneryController; compose FontController; Text(int x, int y, string message); };
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 FTP-023: Compositional Model.
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() -> (Time time); CurrentTimeZone() -> (string timezone); }
And an Horologist
protocol that sets the time and timezone:
protocol Horologist { SetTime(Time time); SetCurrentTimeZone(string timezone); }
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
) which composes both:
protocol SystemClock { compose Clock; compose Horologist; }
Type aliasing is supported. For example:
using StoryID = string:MAX_SIZE; using up_to_five = vector: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 up_to_five
is an alias for a vector declaration of five elements.
The identifiers StoryID
and up_to_five
can be used wherever their aliased definitions can be used. Consider:
struct Message { StoryID baseline; up_to_five<StoryID> 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
.
Note that byte
and bytes
are built in aliases, see below.
FIDL provides several built-ins:
byte
and bytes
)zx library
see belowThe types byte
and bytes
are built-in, and are conceptually equivalent to:
library builtin; using byte = uint8; using bytes = vector<byte>;
When you refer to a name without specific scope, e.g.:
struct SomeName { byte here; };
we treat this as builtin.byte
automatically (so long as there isn't a more-specific name in scope).
The fidlc
compiler automatically generates an internal ZX library for you that contains commonly used Zircon definitions.