blob: 277e47bfe96f82fa2912f1be21e7c427125c8bad [file] [log] [blame] [view]
# New C++ bindings
<!-- TODO(https://fxbug.dev/42054534): Document natural bindings here too. -->
The new C++ bindings come in **natural** and **wire** variants. The natural
bindings are optimized for safety and ergonomics, while the wire bindings are
optimized for performance. Most code should should use the natural bindings.
Only turn to the wire bindings when performance requirements demand it or when
you need precise control over memory allocation.
Note: This page currently only documents the wire bindings. See the [New C++
bindings tutorials] and [Comparing new C++ and high-level C++ language bindings]
for examples of the natural bindings.
## Libraries {#libraries}
Given the library declaration:
```fidl
library fuchsia.examples;
```
[Protocol types](#protocols) are generated in the `fuchsia_examples` namespace.
[Domain objects](#type-definitions) for this library are generated in the
`fuchsia_examples::wire` namespace, and [test scaffolding](#test-scaffolding)
is generated in the `fidl::testing` namespace.
Generated type names are transformed to follow the
[Google C++ style guide][cpp-style].
## Constants {#constants}
[Constants][lang-constants] are generated as a `constexpr`. For example, the
following constants:
```fidl
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples/types.test.fidl" region_tag="consts" %}
```
Are generated in the header file as:
```c++
constexpr uint8_t kBoardSize = 9u;
extern const char[] kName;
```
The correspondence between FIDL primitive types and C++ types is outlined in
[built-in types](#builtins). Instead of `constexpr`, strings are declared as an
`extern const char[]` in the header file, and defined in a `.cc` file.
## Fields
This section describes how the FIDL toolchain converts FIDL types to C++ wire
types. These types can appear as members in an aggregate type or as parameters
to a protocol method.
### Built-in types {#builtins}
The FIDL types are converted to C++ types based on the following table:
|FIDL Type|C++ Wire Type|
|--- |--- |
|`bool`|`bool`, *(requires sizeof(bool) == 1)*|
|`int8`|`int8_t`|
|`int16`|`int16_t`|
|`int32`|`int32_t`|
|`int64`|`int64_t`|
|`uint8`|`uint8_t`|
|`uint16`|`uint16_t`|
|`uint32`|`uint32_t`|
|`uint64`|`uint64_t`|
|`float32`|`float`|
|`float64`|`double`|
|`array<T, N>`|`fidl::Array<T, N>`|
|`vector<T>:N`|`fidl::VectorView<T>`|
|`string`|`fidl::StringView`|
|`client_end:P` |`fidl::ClientEnd<P>`|
|`server_end:P` |`fidl::ServerEnd<P>`|
|`zx.Handle`|`zx::handle`|
|`zx.Handle:S`|The corresponding zx type is used whenever possible. For example, `zx::vmo` or `zx::channel`.|
Optional vectors, strings, client/server ends, and handles have the same C++
wire types as their non-optional counterparts.
### User defined types {#user-defined-types}
The C++ wire bindings define a class for each user defined bits, enum, struct,
table, and union. For strict enums, they define an `enum class` instead of a
regular class. For unions, they use the same class to represent optional and
non-optional values. For boxed structs, they use `fidl::ObjectView`. See [Memory
ownership of wire domain objects] to learn more about `fidl::ObjectView`.
## Type definitions {#type-definitions}
### Bits {#bits}
Given the [bits][lang-bits] definition:
```fidl
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples/types.test.fidl" region_tag="bits" %}
```
The FIDL toolchain generates a `FileMode` class with a static member for each
flag, as well as a `kMask` member that contains a mask of all bits members (in
this example `0b111`):
* `const static FileMode kRead`
* `const static FileMode kWrite`
* `const static FileMode kExecute`
* `const static FileMode kMask`
`FileMode` provides the following methods:
* `explicit constexpr FileMode(uint16_t)`: Constructs a value from an underlying
primitive value, preserving any unknown bit members.
* `constexpr static std::optional<FileMode> TryFrom(uint16_t value)`: Constructs
an instance of the bits from an underlying primitive value if the value does
not contain any unknown members, and returns `std::nullopt` otherwise.
* `constexpr static FileMode TruncatingUnknown(uint16_t value)`: Constructs an
instance of the bits from an underlying primitive value, clearing any unknown
members.
* Bitwise operators: Implementations for the `|`, `|=`, `&`, `&=`, `^`, `^=`,
and `~` operators are provided, allowing bitwise operations on the bits like
`mode |= FileMode::kExecute`.
* Comparison operators `==` and `!=`.
* Explicit conversion functions for `uint16_t` and `bool`.
If `FileMode` is [flexible][lang-flexible], it will have the following
additional methods:
* `constexpr FileMode unknown_bits() const`: Returns a bits value that contains
only the unknown members from this bits value.
* `constexpr bool has_unknown_bits() const`: Returns whether this value contains
any unknown bits.
Note: When applying bitwise negation to bits values that contain unknown
members, the resulting bits value is only defined for the known bits.
Example usage:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-bits" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
### Enums {#enums}
Given the [enum][lang-enums] definition:
```fidl
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples/types.test.fidl" region_tag="enums" %}
```
The FIDL toolchain generates a C++ `enum class` using the specified underlying
type, or `uint32_t` if none is specified:
```c++
enum class LocationType : uint32_t {
kMuseum = 1u;
kAirport = 2u;
kRestaurant = 3u;
};
```
Example usage:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-enums" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
#### Flexible enums {#flexible-enums}
Flexible enums are implemented as a `class` instead of an `enum class`, with the
following methods:
* `constexpr LocationType()`: Default constructor, which initializes the enum to
an unspecified unknown value.
* `constexpr LocationType(uint32_t value)`: Explicit constructor that takes in a
value of the underlying type of the enum.
* `constexpr bool IsUnknown()`: Returns whether the enum value is unknown.
* `constexpr static LocationType Unknown()`: Returns an enum value that is
guaranteed to be treated as unknown. If the enum has a member annotated with
[`[Unknown]`][unknown-attr], then the value of that member is returned. If
there is no such member, then the underlying value of the returned enum member
is unspecified.
* `explicit constexpr operator int32_t() const`: Converts the enum back to its
underlying value.
The generated class contains a static member for each enum member, which are
guaranteed to match the members of the `enum class` in the equivalent
[strict][lang-flexible] enum:
* `const static LocationType kMuseum`
* `const static LocationType kAirport`
* `const static LocationType kRestaurant`
### Structs {#structs}
Given the [struct][lang-structs] declaration:
```fidl
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples/types.test.fidl" region_tag="structs" %}
```
The FIDL toolchain generates an equivalent `struct`:
```c++
struct Color {
uint32_t id = {};
fidl::StringView name = {};
}
```
The C++ bindings do not support default values, and instead they zero-initialize
all fields of the struct.
Example usage:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-structs" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
### Unions {#unions}
Given the union definition:
```fidl
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples/types.test.fidl" region_tag="unions" %}
```
FIDL will generate a `JsonValue` class. `JsonValue` contains a public tag enum
class representing the possible [variants][union-lexicon]:
```c++
enum class Tag : fidl_xunion_tag_t {
kIntValue = 2,
kStringValue = 3,
};
```
Each member of `Tag` has a value matching its ordinal specified in the `union`
definition.
`JsonValue` provides the following methods:
* `JsonValue()`: Default constructor. The constructed union is initially in an
"absent" state until a variant is set. The `WithFoo` constructors should be
preferred whenever possible.
* `~JsonValue()`: Destructor that clears the underlying union data.
* `JsonValue(JsonValue&&)`: Default move constructor.
* `JsonValue& operator=(JsonValue&&)`: Default move assignment
* `static JsonValue WithIntValue(fidl::ObjectView<int32>)` and `static
JsonValue WithStringValue(fidl::ObjectView<fidl::StringView>)`: Static
constructors that directly construct a specific variant of the union.
* `bool has_invalid_tag()`: Returns `true` if the instance of `JsonValue` does
not yet have a variant set. Calling this method without first setting the
variant leads to an assertion error.
* `bool is_int_value() const` and `bool is_string_value() const`: Each variant
has an associated method to check whether an instance of `JsonValue` is of
that variant
* `const int32_t& int_value() const` and `const fidl::StringView& string_value()
const`: Read-only accessor methods for each variant. Calling these methods
without first setting the variant leads to an assertion error.
* `int32_t& int_value()` and `fidl::StringView& string_value()`: Mutable
accessor methods for each variant. These methods will fail if `JsonValue` does
not have the specified variant set
* `Tag Which() const`: returns the current [tag][union-lexicon] of the
`JsonValue`. Calling this method without first setting the variant leads to an
assertion error.
Example usage:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-unions" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
#### Flexible unions and unknown variants
[Flexible][lang-flexible] unions have an extra variant in the generated `Tag`
class:
```c++
enum class Tag : fidl_xunion_tag_t {
... // other fields omitted
kUnknown = ::std::numeric_limits<::fidl_union_tag_t>::max(),
};
```
When a FIDL message containing a union with an unknown variant is decoded into
`JsonValue`, `JsonValue::Which()` will return `JsonValue::Tag::kUnknown`.
The C++ bindings bindings do not store the raw bytes and handles of unknown
variants.
Encoding a union with an unknown variant is not supported and will cause
an encoding failure.
### Tables {#tables}
Given the [table][lang-tables] definition:
```fidl
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples/types.test.fidl" region_tag="tables" %}
```
The FIDL toolchain generates a `User` class with the following methods:
* `User()`: Default constructor, initializes an empty table with no fields set.
* `User::Builder(fidl::AnyArena& arena)`: Builder factory.
Returns a `fidl::WireTableBuilder<User>` that allocates the frame and members
from the supplied arena.
* `User::ExternalBuilder(fidl::ObjectView<fidl::WireTableFrame<User>> frame)`:
External builder factory. Returns a `fidl::WireTableExternalBuilder<User>`
with the supplied frame. This builder requires careful, memory management but
might occasionally be useful. _Caveat Emptor_.
* `User(User&&)`: Default move constructor.
* `~User()`: Default destructor.
* `User& operator=(User&&)`: Default move assignment.
* `bool IsEmpty() const`: Returns true if no fields are set.
* `bool has_age() const` and `bool has_name() const`: Returns whether a field is
set.
* `const uint8_t& age() const` and `const fidl::StringView& name() const`:
Read-only field accessor methods. Calling these methods without first setting
the field leads to an assertion error.
In order to build a table, three additional class is generated:
`fidl::WireTableBuilder<User>`, `fidl::WireTableExternalBuilder<User>` and
`fidl::WireTableFrame<User>`.
`fidl::WireTableFrame<User>` is a container for the table's internal storage,
and is allocated separately from the builder to maintain the object layout of
the underlying wire format. It is only use internally by builders.
`fidl::WireTableFrame<User>` has the following methods:
* `WireTableFrame()`: Default constructor.
`fidl::WireTableExternalBuilder<User>` has the following methods:
* `fidl::WireTableExternalBuilder<User> age(uint8_t)`:
set age by inlining it into the table frame.
* `fidl::WireTableExternalBuilder<User> name(fidl::ObjectView<fidl::StringView>)`:
set name with an already allocated value.
* `User Build()`: build and return the table object. After `Build()` is called
the builder must be discarded.
`fidl::WireTableBuilder<User>` has all of the methods of
`fidl::WireTableExternalBuilder<User>` (but with the right return type from the
setters) and adds:
* `fidl::WireTableBuilder<User> name(std::string_view)`: set name by allocating
a new `fidl::StringView` from the builder's arena and copying the supplied
string into it.
Example usage:
```c++
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-tables" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}
```
In addition to assigning fields with `fidl::ObjectView`, you can use any of the
allocation strategies described in [Memory ownership of wire domain objects].
Note: Tables with unknown fields will decode successfully but will fail to
encode.
### Inline layouts
The generated C++ code uses the [the name chosen by `fidlc`][anon-names] for
inline layouts.
The C++ bindings also generate scoped names to refer to inline layouts. For
example, for the FIDL:
```fidl
type Outer = struct {
inner struct {};
};
```
The inner struct can be referred to using its globally unique name `Inner` as
well as the scoped name `Outer::Inner`. This can be useful when the top level
name is overridden using the [`@generated_name`][generated-name-attr] attribute,
for example in:
```fidl
type Outer = struct {
inner
@generated_name("SomeCustomName") struct {};
};
```
the inner struct can be referred to as `SomeCustomName` or `Outer::Inner`.
Another example of this is the [protocol result](#protocol-results) types: the
success and error variants of a type such as `TicTacToe_MakeMove_Result` can be
referenced as `TicTacToe_MakeMove_Result::Response` and
`TicTacToe_MakeMove_Result::Err`, respectively.
## Protocols {#protocols}
Given the [protocol][lang-protocols]:
```fidl
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples/types.test.fidl" region_tag="protocols" %}
```
Note: The `MakeMove` method above returns a bool representing success, and a
nullable response value. This is considered un-idiomatic, you should use an
[error type](#protocols-results) instead.
FIDL will generate a `TicTacToe` class, which acts as an entry point for types
and classes that both clients and servers will use to interact with this
service. The members of this class are described in individual subsections in
the rest of this section.
### Typed channel endpoints {#typed-channels}
The C++ bindings send and receive FIDL protocol messages over the [Zircon
channel][zircon-channel] transport, which carry arbitrary blobs of bytes and
handles. Rather than exposing raw endpoints, for instance `zx::channel`, the API
exposes three templated endpoint classes:
* `fidl::ClientEnd<TicTacToe>`: the client endpoint of the `TicTacToe` protocol;
it owns a `zx::channel`. Client bindings that require exclusive ownership of
the channel would consume this type. For example, a
`fidl::WireClient<TicTacToe>` may be constructed from a
`fidl::ClientEnd<TicTacToe>`, also known as "binding the channel to the
message dispatcher".
* `fidl::UnownedClientEnd<TicTacToe>`: an unowned value borrowing some client
endpoint of the `TicTacToe` protocol. Client APIs that do not require
exclusive ownership of the channel would take this type. An `UnownedClientEnd`
may be derived from a `ClientEnd` of the same protocol type by calling
`borrow()`. The borrowed-from endpoint may be `std::move`-ed within the same
process, but cannot be dropped or transferred out-of-process, while there are
unowned borrows to it.
* `fidl::ServerEnd<TicTacToe>`: the server endpoint of the `TicTacToe` protocol;
it owns a `zx::channel`. Server bindings that require exclusive ownership of
the channel would consume this type. For example, a
`fidl::ServerEnd<TicTacToe>` may be provided to `fidl::BindServer<TicTacToe>`
among other parameters to create a server binding.
There is no `UnownedServerEnd` as it is not yet needed to safely implement the
current set of features.
A pair of client and server endpoint may be created using the
`::fidl::CreateEndpoints<TicTacToe>` library call. In a protocol request
pipelining scenario, one can immediately start performing operations on the
client endpoint after `std::move()`-ing the server endpoint to the remote
server.
See the class documentation on these types for more details.
### Request and response types {#request-response-structs}
The request type for a FIDL method or event can be accessed through a pair of
aliases, `fidl::WireRequest` and `fidl::WireEvent`:
* `fidl::WireRequest<TicTacToe::StartGame>`
* `fidl::WireRequest<TicTacToe::MakeMove>`
* `fidl::WireEvent<TicTacToe::OnOpponentMove>`
If the type used for the request or event is a named type, the alias will point
to that type. If the request type was an anonymous type, the alias will point to
the type name generated for that anonymous type. For both method requests and
events, the generated request type is named `[Method]Request`.
Unlike requests, responses to two-way methods are generated as a new type,
`fidl::WireResult`:
* `fidl::WireResult<TicTacToe::MakeMove>`
The `fidl::WireResult` type inherits from `fidl::Status`, and its status tells
whether the call succeeded at the FIDL layer. If the method has a non-empty
response or uses FIDL error syntax, the generated `WireResult` type will also
have a set of accessors for accessing the return value or application-layer
error. The available accessors for the contained result are:
* `WireResultUnwrapType<FidlMethod>* Unwrap()`
* `const WireResultUnwrapType<FidlMethod>* Unwrap() const`
* `WireResultUnwrapType<FidlMethod>& value()`
* `const WireResultUnwrapType<FidlMethod>& value() const`
* `WireResultUnwrapType<FidlMethod>* operator->()`
* `const WireResultUnwrapType<FidlMethod>* operator->() const`
* `WireResultUnwrapType<FidlMethod>& operator*()`
* `const WireResultUnwrapType<FidlMethod>& operator*() const`
The `WireResultUnwrapType` is another type alias, which depends on whether the
method uses error syntax. Given this example library,
```fidl
library response.examples;
protocol Test {
Foo() -> (struct { x int32; });
Bar() -> () error int32;
Baz() -> (struct { x int32; }) error int32;
};
```
here is what the `fidl::WireResultUnwrapType` is for each method in the `Test`
protocol:
* `fidl::WireResultUnwrapType<response_examples::Test::Foo> = response_examples::wire::TestFooResponse`
* `fidl::WireResultUnwrapType<response_examples::Test::Bar> = fit::result<int32_t>`
* `fidl::WireResultUnwrapType<response_examples::Test::Baz> = fit::result<int32_t, ::response_examples::wire::TestBazResponse*>`
### Client {#client}
The C++ wire bindings bindings provides multiple ways to interact with a FIDL
protocol as a client:
* `fidl::WireClient<TicTacToe>`: This class exposes thread-safe APIs for
outgoing asynchronous and synchronous calls as well as asynchronous event
handling. It owns the client end of the channel. An `async_dispatcher_t*` is
required to support the asynchronous APIs as well as event and error handling.
It must be used with a single-threaded dispatcher. Objects of this class must
be bound to the client endpoint and destroyed on the same thread that is
running the dispatcher. This is the recommended variant for most use cases,
except for those where an `async_dispatcher_t` cannot be used or when the
client needs to be moved between threads.
* `fidl::WireSharedClient<TicTacToe>`: This class has less opinions on threading
models compared to `WireClient`, but requires a two-phase shutdown pattern to
prevent use-after-frees. Objects of this class may be destroyed on an
arbitrary thread. It also supports use with a multi-threaded dispatcher. For
more details, see the [New C++ bindings threading guide].
* `fidl::WireSyncClient<TicTacToe>`: This class exposes purely synchronous APIs
for outgoing calls as well as for event handling. It owns the client end of
the channel.
* `fidl::WireCall<TicTacToe>`: This class is identical to `WireSyncClient`
except that it does not have ownership of the client end of the channel.
`WireCall` may be preferable to `WireSyncClient` when implementing C APIs that
take raw `zx_handle_t`s.
#### WireClient {#async-client}
<!-- TODO(https://fxbug.dev/42136655) fidl::WireClient should be covered by generated docs -->
`fidl::WireClient` is thread-safe and supports both synchronous and asynchronous
calls as well as asynchronous event handling.
##### Creation
A client is created with a client-end `fidl::ClientEnd<P>` to the protocol `P`,
an `async_dispatcher_t*`, and an optional pointer to an
[`WireAsyncEventHandler`](#async-event-handlers) that defines the methods to be
called when a FIDL event is received or when the client is unbound. If the
virtual method for a particular event is not overridden, the event is ignored.
```cpp
class EventHandler : public fidl::WireAsyncEventHandler<TicTacToe> {
public:
EventHandler() = default;
void OnOpponentMove(fidl::WireEvent<OnOpponentMove>* event) override {
/* ... */
}
void on_fidl_error(fidl::UnbindInfo unbind_info) override { /* ... */ }
};
fidl::ClientEnd<TicTacToe> client_end = /* logic to connect to the protocol */;
EventHandler event_handler;
fidl::WireClient<TicTacToe> client;
client.Bind(std::move(client_end), dispatcher, &event_handler);
```
The binding may be torn down automatically in case of the server-end being
closed or due to an invalid message being received from the server. You may also
actively tear down the bindings by destroying the client object.
##### Outgoing FIDL methods
You can invoke outgoing FIDL APIs through the `fidl::WireClient` instance.
Dereferencing a `fidl::WireClient` provides access to the following methods:
* For `StartGame` (fire and forget):
* `fidl::Status StartGame(bool start_first)`: Managed variant of a fire and
forget method.
* For `MakeMove` (two way):
* `[...] MakeMove(uint8_t row, uint8_t col)`: Managed variant of an
asynchronous two way method. It returns an internal type that must be used
to register the asynchronous continuation for receiving the result, such
as a callback. See [specifying asynchronous
continuation][specifying-asynchronous-continuation]. The continuation will
be executed on a dispatcher thread unless the dispatcher is shutting down.
`fidl::WireClient::buffer` provides access to the following methods:
* `fidl::Status StartGame(bool start_first)`: Caller-allocated variant of a fire
and forget method.
* `[...] MakeMove(uint8_t row, uint8_t col)`: Asynchronous, caller-allocated
variant of a two way method. It returns the same internal type as that from
the managed variant.
`fidl::WireClient::sync` provides access to the following methods:
* `fidl::WireResult<MakeMove> MakeMove(uint8_t row, uint8_t col)`: Synchronous,
managed variant of a two way method. The same method exists on
`WireSyncClient`.
##### Specifying asynchronous continuation {#specifying-asynchronous-continuation}
See the corresponding C++ [documentation comments][wire-thenable-impl].
The continuation is called with a result object either representing a
successfully decoded response or an error. This is useful when the user needs to
propagate errors for each FIDL call to their originators. For example, a server
may need to make another FIDL call while handling an existing FIDL call, and
need to fail the original call in case of errors.
The are a few methods on the returned object from a two way call:
* `Then`: takes a callback, and invokes the callback at most once until the
client goes away.
* `ThenExactlyOnce`: when passed a callback, the callback is executed exactly
once, either when the call succeeds or fails. However, because the callbacks
are invoked asynchronously, be ware of [use-after-free bugs when destroying a
client][result-callback-use-after-free]: the objects captured by the callback
may not be valid.
* `ThenExactlyOnce` may also take a response context when control over
allocation is desired. `TicTacToe` has only one response context,
`fidl::WireResponseContext<TicTacToe::MakeMove>`, which has pure virtual
methods that should be overridden to handle the result of the call:
```c++
virtual void OnResult(fidl::WireUnownedResult<MakeMove>& result) = 0;
```
`OnResult` is called with a result object either representing a successfully
decoded response or an error. You are responsible for ensuring that the response
context object outlives the duration of the entire async call, since the
`fidl::WireClient` borrows the context object by address to avoid implicit
allocation.
##### Centralized error handler {#central-error-handler}
When the binding is torn down due to an error,
`fidl::WireAsyncEventHandler<TicTacToe>::on_fidl_error` will be invoked from the
dispatcher thread with the detailed reason. When the error is dispatcher
shutdown, `on_fidl_error` will be invoked from the thread that is calling
dispatcher shutdown. It is recommended to put any central logic for logging or
releasing resources in that handler.
#### WireSyncClient {#sync-client}
`fidl::WireSyncClient<TicTacToe>` is a synchronous client which provides the
following methods:
* `explicit WireSyncClient(fidl::ClientEnd<TicTacToe>)`: Constructor.
* `~WireSyncClient()`: Default destructor.
* `WireSyncClient(&&)`: Default move constructor.
* `WireSyncClient& operator=(WireSyncClient&&)`: Default move assignment.
* `const fidl::ClientEnd<TicTacToe>& client_end() const`: Returns the underlying
[client endpoint](#typed-channels).
* `fidl::Status StartGame(bool start_first)`: Managed variant of a fire and
forget method call. Buffer allocation for requests are entirely handled within
this function.
* `fidl::WireResult<TicTacToe::MakeMove> MakeMove(uint8_t row, uint8_t col)`:
Managed variant of a two way method call, which takes the parameters as
arguments and returns a `WireResult` object. Buffer allocation for requests
and responses are entirely handled within this function. The bindings
calculate a safe buffer size specific to this call at compile time based on
FIDL wire-format and maximum length constraints. The buffers are allocated on
the stack if they fit under 512 bytes, or else on the heap. See
[WireResult](#result) for details on buffer management.
* `fidl::Status HandleOneEvent(SyncEventHandler& event_handler)`: Blocks to
consume exactly one event from the channel. If the server has sent an epitaph,
the status contained within the epitaph is returned. See [Events](#events).
`fidl::WireSyncClient<TicTacToe>::buffer` provides the following methods:
* `fidl::WireUnownedResult<TicTacToe::StartGame> StartGame(bool start_first)`:
Caller-allocated variant of a fire and forget call, which takes in backing
storage for the request buffer passed as the argument to `buffer`, as well as
request parameters, and returns an `fidl::WireUnownedResult`.
* `fidl::WireUnownedResult<TicTacToe::MakeMove> MakeMove(uint8_t row, uint8_t
col)`: Caller-allocated variant of a two way method, which requests both the
space for encoding the request and the space for receiving the response from
the same memory resource that is passed to the `buffer` method.
Note that each method has both an owned and caller-allocated variant. In brief,
the owned variant of each method handles memory allocation for requests and
responses, whereas the caller-allocated variant allows the user to provide the
buffer themselves. The owned variant is easier to use, but may result in extra
allocation.
#### WireCall {#client-call}
`fidl::WireCall<TicTacToe>` provides similar methods to those found in
`WireSyncClient`, with the only difference being that `WireCall` can be
constructed with a `fidl::UnownedClientEnd<TicTacToe>` i.e. it borrows the
client endpoint:
* `fidl::WireResult<StartGame> StartGame(bool start_first)`: Owned variant of
`StartGame`.
* `fidl::WireResult<MakeMove> MakeMove(uint8_t row, uint8_t col)`: Owned variant
of `MakeMove`.
`fidl::WireCall<TicTacToe>(client_end).buffer` provides the following methods:
* `fidl::WireUnownedResult<StartGame> StartGame(bool start_first)`:
Caller-allocated variant of `StartGame`.
* `fidl::WireUnownedResult<MakeMove> MakeMove(uint8_t row, uint8_t col);`:
Caller-allocated variant of `MakeMove`.
#### Result, WireResult, and WireUnownedResult {#result}
The managed variants of each method of `WireSyncClient` and `WireCall` all
return a `fidl::WireResult<Method>` type, whereas the caller-allocating variants
all return an `fidl::WireUnownedResult<Method>`. Fire and forget methods on
`fidl::WireClient` return a `fidl::Status`. These types define the same set of
methods:
* `zx_status status() const` returns the transport status. it returns the
first error encountered during (if applicable) linearizing, encoding, making
a call on the underlying channel, and decoding the result. If the status is
`ZX_OK`, the call has succeeded, and vice versa.
* `fidl::Reason reason() const` returns details about which operation failed,
when `status()` is not `ZX_OK`. For example, if encoding failed, `reason()`
will return `fidl::Reason::kEncodeError`. `reason()` should not be called
when status is `ZX_OK`.
* `const char* error_message() const` contains a brief error message when
status is not `ZX_OK`. Otherwise, returns `nullptr`.
* **(only for WireResult and WireUnownedResult for two-way calls)** `T* Unwrap()`
returns a pointer to the [response struct](#request-response-structs). For
`WireResult`, the pointer points to memory owned by the result object. For
`WireUnownedResult`, the pointer points to the caller-provided buffer.
`Unwrap()` should only be called when the status is `ZX_OK`.
Additionally, `WireResult` and `WireUnownedResult` for two-way calls will
implement dereference operators that return the response struct itself.
This allows code such as:
```cpp
fidl::WireResult result = client.sync()->MakeMove(0, 0);
auto* response = result.Unwrap();
bool success = response->success;
```
To be simplified to:
```cpp
fidl::WireResult result = client.sync()->MakeMove(0, 0);
bool success = result->success;
```
> `WireResult<Method>` manages ownership of all buffer and handles, while
> `::Unwrap()` returns a view over it. Therefore, this object must outlive any
> references to the unwrapped response.
##### Allocation strategy And move semantics
`WireResult` stores the response buffer inline if the message is guaranteed to
fit under 512 bytes. Since the result object is usually instantiated on the
caller's stack, this effectively means the response is stack-allocated when it
is reasonably small. If the maximal response size exceeds 512 bytes,
`WireResult` instead contains a heap-allocated buffer.
Therefore, `std::move()` on `WireResult` is not supported. The content has to be
copied if the buffer is inline, and pointers to out-of-line objects have to be
updated to locations within the destination object, these are surprising
overheads for a move operation that is commonly understood to be low cost.
If the result object need to be passed around multiple function calls, consider
pre-allocating a buffer in the outer-most function and use the caller-allocating
flavor.
### Server
Implementing a server for a FIDL protocol involves providing a concrete
implementation of `TicTacToe`.
The generated `fidl::WireServer<TicTacToe>` class has pure virtual methods
corresponding to the method calls defined in the FIDL protocol. Users implement
a `TicTacToe` server by providing a concrete implementation of
`fidl::WireServer<TicTacToe>`, which has the following pure virtual methods:
* `virtual void StartGame(StartGameRequestView request, StartGameCompleter::Sync&
completer)`
* `virtual void MakeMove(MakeMoveRequestView request, MakeMoveCompleter::Sync&
completer)`
Refer to the [example C++ server][cpp-server-example] for how to bind and
set up a server implementation.
The C++ wire bindings also provide functions for manually dispatching a message
given an implementation, `fidl::WireDispatch<TicTacToe>`:
* `void fidl::WireDispatch<TicTacToe>(fidl::WireServer<TicTacToe>* impl,
fidl::IncomingMessage&& msg, ::fidl::Transaction* txn)`: Dispatches the
incoming message. If there is no matching handler, it closes all handles in
the message and notifies `txn` of an error.
#### Requests {#server-requests}
The request is provided as the first argument of each generated FIDL method
handler. This a view of the request (a pointer). All the request arguments are
accessed using the arrow operator and the argument name.
For example:
* `request->start_first`
* `request->row`
See [Memory ownership of wire domain objects] for notes on request lifetime.
#### Completers {#server-completers}
A completer is provided as the last argument of each generated FIDL method
handler, after all the FIDL request parameters for that method. The completer
classes capture the various ways one can complete a FIDL transaction, e.g. by
sending a reply, closing the channel with an epitaph, etc, and come in both
synchronous and asynchronous versions (though the `::Sync` class is provided as
an argument by default). In this example, this completers are:
* `fidl::WireServer<TicTacToe>::StartGameCompleter::Sync`
* `fidl::WireServer<TicTacToe>::StartGameCompleter::Async`
* `fidl::WireServer<TicTacToe>::MakeMoveCompleter::Sync`
* `fidl::WireServer<TicTacToe>::MakeMoveCompleter::Async`
All completer classes provide the following methods:
* `void Close(zx_status_t status)`: Close the channel and send `status` as the
epitaph.
In addition, two way methods will provide two versions of a `Reply` method for
replying to a response: a managed variant and a caller-allocating variant. These
correspond to the variants present in the [client API](#client). For example,
both `MakeMoveCompleter::Sync` and `MakeMoveCompleter::Async` provide the
following `Reply` methods:
* `::fidl::Status Reply(bool success, fidl::ObjectView<GameState> new_state)`
* `::fidl::Status Reply(fidl::BufferSpan _buffer, bool success,
fidl::ObjectView<GameState> new_state)`
Because the status returned by Reply is identical to the unbinding status, it
can be safely ignored.
Finally, sync completers for two way methods can be converted to an async
completer using the `ToAsync()` method. Async completers can out-live the scope
of the handler by e.g. moving it into a lambda capture, allowing the server to
respond to requests asynchronously. The async completer has the same methods for
responding to the client as the sync completer. See [Responding to requests
asynchronously] for example usage
Note: Each `Completer` object must only be accessed by one thread at a time.
Simultaneous access from multiple threads will result in a crash.
##### Parallel message handling
By default, messages from a single binding are handled sequentially, i.e. a
single thread attached to the dispatcher (run loop) is woken up if necessary,
reads the message, executes the handler, and returns back to the dispatcher. The
`::Sync` completer provides an additional API, `EnableNextDispatch()`, which may
be used to selectively break this restriction. Specifically, a call to this API
will enable another thread waiting on the dispatcher to handle the next message
on the binding while the first thread is still in the handler. Note that
repeated calls to `EnableNextDispatch()` on the same `Completer` are idempotent.
Note: This use-case is currently possible only using the
[lib/fidl](/sdk/lib/fidl/cpp/wire) bindings.
```cpp
void DirectedScan(int16_t heading, ScanForPlanetsCompleter::Sync& completer) override {
// Suppose directed scans can be done in parallel. It would be suboptimal to block one scan until
// another has completed.
completer.EnableNextDispatch();
fidl::VectorView<Planet> discovered_planets = /* perform a directed planet scan */;
completer.Reply(std::move(discovered_planets));
}
```
### Caller-allocated methods
A number of the APIs above provide owned and caller-allocated variants of
generated methods.
The caller-allocated variant defers all memory allocation responsibilities to
the caller. The type `fidl::BufferSpan` references a buffer address and size. It
will be used by the bindings library to construct the FIDL request, hence it
must be sufficiently large. The method parameters (e.g. `heading`) are
*linearized* to appropriate locations within the buffer. There are a number of
ways to create the buffer:
```cpp
// 1. On the stack
using StartGame = TicTacToe::StartGame;
fidl::SyncClientBuffer<StartGame> buffer;
auto result = client.buffer(buffer.view())->StartGame(true);
// 2. On the heap
auto buffer = std::make_unique<fidl::SyncClientBuffer<StartGame>>();
auto result = client.buffer(buffer->view())->StartGame(true);
// 3. Some other means, e.g. thread-local storage
constexpr uint32_t buffer_size = fidl::SyncClientMethodBufferSizeInChannel<StartGame>();
uint8_t* buffer = allocate_buffer_of_size(buffer_size);
fidl::BufferSpan buffer_span(/* data = */buffer, /* capacity = */request_size);
auto result = client.buffer(buffer_span)->StartGame(true);
// Check the transport status (encoding error, channel writing error, etc.)
if (result.status() != ZX_OK) {
// Handle error...
}
// Don't forget to free the buffer at the end if approach #3 was used...
```
> When the caller-allocating flavor is used, the `result` object borrows the
> request and response buffers (hence its type is under `WireUnownedResult`).
> Make sure the buffers outlive the `result` object.
> See [WireUnownedResult](#result).
Note: buffers passed to the bindings must be aligned to 8 bytes. The
`fidl::SyncClientBuffer` helper class does this automatically. For an
asynchronous client, use `fidl::AsyncClientBuffer`. Failure to align would
result in a run-time error.
### Events {#events}
In the C++ bindings, events can be handled asynchronously or synchronously,
depending on the type of [client](#client) being used.
#### Async client {#async-event-handlers}
When using a `fidl::WireClient`, events can be handled asynchronously by passing
the class a `fidl::WireAsyncEventHandler<TicTacToe>*`. The
`WireAsyncEventHandler` class has the following members:
* `virtual void OnOpponentMove(fidl::WireEvent<OnOpponentMove>* event) {}`:
handler for the OnOpponentMove event (one method per event).
* `virtual on_fidl_error(::fidl::UnbindInfo info) {}`: method called when the
client encounters a terminal error.
To be able to handle events and errors, a class that inherits from
`fidl::WireAsyncEventHandler<TicTacToe>` must be defined.
#### Sync client {#sync-event-handlers}
In `WireSyncClient`, events are handled synchronously by calling
a `HandleOneEvent` function and passing it a
`fidl::WireSyncEventHandler<TicTacToe>`.
`WireSyncEventHandler` is a class that contains a pure virtual method for each
event. In this example, it consists of the following member:
* `virtual void OnOpponentMove(fidl::WireEvent<TicTacToe::OnOpponentMove>*
event) = 0`: The handle for the OnOpponentMove event.
To be able to handle events, a class that inherits from `WireSyncEventHandler`
must be defined. This class must define the virtual methods for all the events
in the protocol. Then an instance of this class must be created.
There are two ways to handle one event. Each one use an instance of the user
defined event handler class:
* `::fidl::Status fidl::WireSyncClient<TicTacToe>::HandleOneEvent(
SyncEventHandler& event_handler)`: A bound version for sync clients.
* `::fidl::Status fidl::WireSyncEventHandler<TicTacToe>::HandleOneEvent(
fidl::UnownedClientEnd<TicTacToe> client_end)`: An unbound version that
uses an `fidl::UnownedClientEnd<TicTacToe>` to handle one event for a specific
handler.
For each call to `HandleOneEvent`, the method waits on the channel for exactly
one incoming message. Then the message is decoded. If the result is
`fidl::Status::Ok()` then exactly one virtual method has been called. Otherwise,
no virtual method has been called and the status indicates the error.
If the handlers are always the same (from one call to `HandleOneEvent` to the
other), the `WireSyncEventHandler` object should be constructed once and used
each time you need to call `HandleOneEvent`.
If an event is marked as transitional, then a default implementation is
provided, which causes `HandleOneEvent` to return an error upon receiving a
transitional event that is not handled by the user.
#### Server
`fidl::WireSendEvent` is used to send events from the server side. There are two
overloads:
* `fidl::WireSendEvent(const fidl::ServerBindingRef<Protocol>& binding_ref)`
to send events over a server binding reference.
* `fidl::WireSendEvent(const fidl::ServerEnd<Protocol>& endpoint)`
to send events over an endpoint.
##### Sending events using a server binding object {#bound-event-sending}
When binding a server implementation to a channel, `fidl::BindServer` returns a
`fidl::ServerBindingRef<Protocol>`, which is the means by which you may interact
safely with a server binding.
Calling `fidl::WireSendEvent` with a binding reference returns an interface to
send events.
The event sender interface contains methods for sending each event. As a
concrete example, the event sender interface for `TicTacToe` provides the
following methods:
* `fidl::Status OnOpponentMove(GameState new_state)`: Managed flavor.
Calling `.buffer(...)` returns a similar interface for the caller-allocating
flavor, allocating encoding buffers from the memory resource passed to
`.buffer`, analogous to the [client API](#client) as well as the [server
completers](#server-completers).
##### Sending events with a ServerEnd object
A server endpoint by itself is represented by `fidl::ServerEnd<Protocol>`.
[Sending events using a server binding object](#bound-event-sending) is the
standard approach to sending events while the server endpoint is bound to an
implementation. However, there may be occasions which call for sending events
on a `fidl::ServerEnd<TicTacToe>` object directly, without setting up a server
binding.
`fidl::WireSendEvent` takes a constant reference to `fidl::ServerEnd<Protocol>`.
It does not support `zx::unowned_channel`, to reduce the chances of using an
endpoint after the handle has been closed elsewhere.
### Results {#protocols-results}
Given a method:
```fidl
protocol TicTacToe {
MakeMove(struct {
row uint8;
col uint8;
}) -> (struct {
new_state GameState;
}) error MoveError;
};
```
FIDL will generate convenience methods on the [completers](#server-completers)
corresponding to methods with an error type. Depending on the Reply "variant",
the completer will have `ReplySuccess`, `ReplyError`, or both methods to respond
directly with the success or error data, without having to construct a union.
For the managed flavor, both methods are available:
* `void ReplySuccess(GameState new_state)`
* `void ReplyError(MoveError error)`
Since `ReplyError` doesn't require heap allocation, only `ReplySuccess` exists
for the caller-allocated flavor:
* `void ReplySuccess(fidl::BufferSpan _buffer, GameState new_state)`
Note that the passed in buffer is used to hold the *entire* response, not just
the data corresponding to the success variant.
The regularly generated `Reply` methods are available as well:
* `void Reply(TicTacToe_MakeMove_Result result)`: Owned variant.
* `void Reply(fidl::BufferSpan _buffer, TicTacToe_MakeMove_Result result)`:
Caller-allocated variant.
The owned and caller-allocated variant use a parameter of
`TicTacToe_MakeMove_Result`, which is generated as a [union](#unions) with two
variants: `Response`, which is a `TicTacToe_MakeMove_Response`, and `Err`, which
is a `MoveError`. `TicTacToe_MakeMove_Response` is generated as a
[struct](#structs) with the response parameters as its fields. In this case, it
has a single field `new_state`, which is a `GameState`.
### Unknown interaction handling {#unknown-interaction-handling}
#### Server-side
When a protocol is declared as `open` or `ajar`, the generated
`fidl::WireServer<Protocol>` type will also inherit from
`fidl::UnknownMethodHandler<Protocol>`. The `UnknownMethodHandler` defines a
single abstract method which the server must implement, called
`handle_unknown_method`, with this signature:
```c++
virtual void handle_unknown_method(UnknownMethodMetadata<Protocol> metadata,
UnknownMethodCompleter::Sync& completer) = 0;
```
The provided `UnknownMethodMetadata` is a struct with one or two fields
depending on if the protocol is `ajar` or `open`. Here's what the metadata
struct looks like, with template arguments omitted for simplicity:
```c++
struct UnknownMethodMetadata {
// Ordinal of the method that was called.
uint64_t method_ordinal;
// Whether the method that was called was a one-way method or a two-way
// method. This field is only defined if the protocol is open, since ajar
// protocols only handle one-way methods.
UnknownMethodType unknown_method_type;
};
```
`UnknownMethodType` is an enum with two variants, `kOneWay` and `kTwoWay`, which
tells which kind of method was called.
The `UnknownMethodCompleter` is the same completer type as is used for one-way
methods.
#### Client-side
There is no way for the client to tell if a `flexible` one-way method was known to
the server or not. For `flexible` two-way methods, if the method is not known to
the server the `fidl::WireResult` will have a `fidl::Status` of
`ZX_ERR_NOT_SUPPORTED` with a reason of `fidl::Reason::kUnknownMethod`. The
`kUnknownMethod` reason is only possible for a flexible two-way method.
Aside from the possibility of getting an error with `Reason` of
`kUnknownMethod`, there are no API differences between `strict` and `flexible`
methods on the client.
For `open` and `ajar` protocols, the generated
`fidl::WireAsyncEventHandler<Protocol>` and
`fidl::WireSyncEventHandler<Protocol>` will inherit from
`fidl::UnknownEventHandler<Protocol>`. `UnknownEventHandler` defines a single
method which event handlers must implement, called `handle_unknown_event`, with
this signature:
```c++
virtual void handle_unknown_event(UnknownEventMetadata<Protocol> metadata) = 0;
```
The `UnknownEventMetadata` has this layout, with template arguments omitted for
simplicity:
```c++
struct UnknownEventMetadata {
// Ordinal of the event that was received.
uint64_t event_ordinal;
};
```
### Protocol composition {#protocol-composition}
FIDL does not have a concept of inheritance, and generates full code as
described above for all [composed protocols][lang-protocol-composition]. In
other words, the code generated for
```fidl
protocol A {
Foo();
};
protocol B {
compose A;
Bar();
};
```
Provides the same API as the code generated for:
```fidl
protocol A {
Foo();
};
protocol B {
Foo();
Bar();
};
```
The generated code is identical except for the method ordinals.
### Protocol and method attributes {#protocol-and-method-attributes}
#### Transitional
For protocol methods annotated with the
[`@transitional`](/docs/reference/fidl/language/attributes.md#transitional)
attribute, the `virtual` methods on the protocol class come with a default
`Close(ZX_NOT_SUPPORTED)` implementation. This allows implementations of the
protocol class with missing method overrides to compile successfully.
#### Discoverable
A protocol annotated with the
[`@discoverable`](/docs/reference/fidl/language/attributes.md#discoverable)
attribute causes the FIDL toolchain to generate an additional `static const char
Name[]` field on the protocol class, containing the full protocol name.
## Persistence, and standalone use of the FIDL wire format
Standalone use of the FIDL wire format, such as encoding and decoding individual
FIDL domain objects, are not yet supported (https://fxbug.dev/42163274).
## Test scaffolding {#test-scaffolding}
The FIDL toolchain also generates a file named `wire_test_base.h` that contains
convenience code for testing FIDL client and server implementations. To use
these headers, depend on the bindings target label with a `_testing` suffix
(`my_library_cpp_testing` instead of `my_library_cpp`).
### Server test base
The test base header contains a class for each protocol that provides stub
implementations for each of the class’ methods, making it possible to implement
only the methods that are used during testing. These classes are template
specializations of `fidl::testing::WireTestBase<Protocol>` or
`fidl::testing::TestBase<Protocol>` where `Protocol` is the FIDL protocol that
is stubbed (e.g. for protocol `games.tictactoe/TicTacToe`, the test bases are
`fidl::testing::WireTestBase<games_tictactoe::TicTacToe>` or
`fidl::testing::TestBase<games_tictactoe::TicTacToe>`).
For the same `TicTacToe` protocol listed above, generated test base subclasses
`fidl::WireServer<TicTacToe>` and `fidl::Server<TicTacToe>` (see
[Protocols](#protocols)), offering the following methods:
* `virtual ~WireTestBase() = default` or `virtual ~TestBase() = default` :
Destructor.
* `virtual void NotImplemented_(const std::string& name, ::fidl::CompleterBase&
completer) = 0`: Pure virtual method that is overridden to define behavior for
unimplemented methods.
The test base provides an implementation for the virtual protocol methods
`StartGame` and `MakeMove`, which are implemented to just call
`NotImplemented_("StartGame", completer)` and `NotImplemented_("MakeMove",
completer)`, respectively.
### Synchronous event handler test base
The test base header contains a class for each protocol that provides stub
implementations for each of the class’ events, making it possible to implement
only the events that are used during testing. Similar to the server test base,
these classes are template specializations of
`fidl::testing::WireSyncEventHandlerTestBase<Protocol>` where `Protocol` is the
FIDL protocol that is stubbed.
For the same `TicTacToe` protocol listed above, generated test base subclasses
`fidl::WireSyncEventHandler<TicTacToe>` (see [Protocols](#protocols)), offering
the following events:
* `virtual ~WireSyncEventHandlerTestBase() = default`: Destructor.
* `virtual void NotImplemented_(const std::string& name) = 0`: Pure virtual
method that is overridden to define behavior for unimplemented events.
The test base provides an implementation for the virtual protocol events
`OnOpponentMove`, which is implemented to just call
`NotImplemented_("OnOpponentMove")`.
[anon-names]: /docs/reference/fidl/language/language.md#inline-layouts
[cpp-style]: https://google.github.io/styleguide/cppguide.html#Naming
[generated-name-attr]: /docs/reference/fidl/language/attributes.md#generated-name
[Memory ownership of wire domain objects]: /docs/development/languages/fidl/tutorials/cpp/topics/wire-memory-ownership.md
[Responding to requests asynchronously]: /docs/development/languages/fidl/tutorials/cpp/topics/async-completer.md
[New C++ bindings threading guide]: /docs/development/languages/fidl/tutorials/cpp/topics/threading.md
[New C++ bindings tutorials]: /docs/development/languages/fidl/tutorials/cpp
[Comparing new C++ and high-level C++ language bindings]: /docs/development/languages/fidl/guides/c-family-comparison.md
[cpp-server-example]: /examples/fidl/cpp/server/wire
[lang-constants]: /docs/reference/fidl/language/language.md#constants
[lang-bits]: /docs/reference/fidl/language/language.md#bits
[lang-enums]: /docs/reference/fidl/language/language.md#enums
[lang-flexible]: /docs/reference/fidl/language/language.md#strict-vs-flexible
[lang-structs]: /docs/reference/fidl/language/language.md#structs
[lang-tables]: /docs/reference/fidl/language/language.md#tables
[lang-unions]: /docs/reference/fidl/language/language.md#unions
[lang-resource]: /docs/reference/fidl/language/language.md#value-vs-resource
[lang-protocols]: /docs/reference/fidl/language/language.md#protocols
[lang-protocol-composition]: /docs/reference/fidl/language/language.md#protocol-composition
[result-callback-use-after-free]: /docs/development/languages/fidl/tutorials/cpp/topics/threading.md#additional_use-after-free_risks_with_thenexactlyonce
[specifying-asynchronous-continuation]: #specifying-asynchronous-continuation
[union-lexicon]: /docs/reference/fidl/language/lexicon.md#union-terms
[unknown-attr]: /docs/reference/fidl/language/attributes.md#unknown
[wire-thenable-impl]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/lib/fidl/cpp/wire/include/lib/fidl/cpp/wire/internal/thenable.h;l=34?q=wirethenable&ss=fuchsia%2Ffuchsia
[zircon-channel]: /docs/reference/kernel_objects/channel.md