Using natural and wire domain objects

Prerequisites

This tutorial builds on the Compiling FIDL tutorial. For more information on other FIDL tutorials, see the overview.

Overview

This tutorial details how to use the natural and wire domain objects by creating a unit test exercising those data types.

This document covers how to complete the following tasks:

Using the domain objects example code

The example code accompanying this tutorial is located in your Fuchsia checkout at //examples/fidl/cpp/domain_objects. It consists of a unit test component and its containing package. For more information about building unit test components, see Build components.

You may build and run the example on a running instance of Fuchsia emulator via the following:

# Add the domain objects unit test to the build.
# This only needs to be done once.
fx set core.x64 --with //examples/fidl/cpp/domain_objects

# Run the domain objects unit test.
fx test -vo fidl-examples-domain-objects-cpp-test

Add the C++ bindings of a FIDL library as a build dependency

  • {GN build}

    For each FIDL library declaration, such as the one in Compiling FIDL, the C++ bindings code for that library is generated under the original target name suffixed with _cpp:

    {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/BUILD.gn" region_tag="binding-dep" adjust_indentation="auto" exclude_regexp="^$" %}
    

    The test target looks like:

    {% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/BUILD.gn" region_tag="test" adjust_indentation="auto" highlight="9" %}
    

    Note the line which adds the dependency on the C++ bindings by referencing that _cpp target.

    (Optional) To view the generated bindings:

    1. Build using fx build.
    2. Change to the generated files directory: out/default/fidling/gen/examples/fidl/fuchsia.examples/fuchsia.examples/cpp/fidl/fuchsia.examples/cpp, where the generated files are located. You may need to change out/default if you have set a different build output directory. You can check your build output directory with cat .fx-build-dir.

    For more information on how to find generated bindings code, see Viewing generated bindings code.

  • {Bazel build}

    When depending on the FIDL library from the Bazel build, an extra build rule is required if the FIDL library is not from the SDK:

    # Given a FIDL library declaration like the following
    fuchsia_fidl_library(
        name = "fuchsia.examples",
        srcs = [
            "echo.test.fidl",
            "types.test.fidl",
        ],
        library = "fuchsia.examples",
        visibility = ["//visibility:public"],
    )
    
    # This rule describes the generated C++ bindings code for that library
    fuchsia_fidl_llcpp_library(
        name = "fuchsia.examples_llcpp_cc",
        library = ":fuchsia.examples",
        visibility = ["//visibility:public"],
        deps = ["@fuchsia_sdk//pkg/fidl_cpp_v2"],
    )
    

    If the FIDL library is from the Bazel SDK, the above step is not needed.

    The C++ bindings code for a FIDL library is generated under the original target name suffixed with _llcpp_cc:

    deps = [
      # Example when depending on an SDK library, `fuchsia.io`.
      "@fuchsia_sdk//fidl/fuchsia.io:fuchsia.io_llcpp_cc",
    
      # Example when depending on a local FIDL library, `fuchsia.examples`
      # defined above.
      # Suppose the library lives in the `//path/to/fidl/library` folder.
      "//path/to/fidl/library:fuchsia.examples_llcpp_cc",
    
      # ... other dependencies ...
    ]
    

Include the bindings header into your code

After adding the build dependency, you may include the bindings header. The include pattern is #include <fidl/my.library.name/cpp/fidl.h>.

The following include statement at the top of domain_objects/main.cc includes the bindings and makes the generated APIs available to the source code:

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="include" %}

Using natural domain objects

Natural types are the ergonomics and safety focused flavor of C++ domain objects. A tree of FIDL values is represented as a tree of C++ objects with hierarchical ownership. That means if a function receives some object of natural type, it can assume unique ownership of all child objects in the entire tree. The tree is torn down when the root object goes out of scope.

At a high level the natural types embrace std:: containers and concepts. For example, a table is represented as a collection of std::optional<Field>s. A vector is std::vector<T>, etc. They also implement idiomatic C++ moves, copies, and equality. For example, a resource type is move-only, while a value type will implement both copy and moves, where moves are designed to optimize the transfer of objects. Moving a table doesn't make it empty (it just recursively moves the fields), similar to std::optional.

Natural bits

Using the strict fuchsia.examples/FileMode FIDL type and the flexible fuchsia.examples/FlexibleFileMode FIDL type as examples:

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="natural-bits" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Natural enums

Using the strict fuchsia.examples/LocationType FIDL type and the flexible fuchsia.examples/FlexibleLocationType FIDL type as examples:

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="natural-enums" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Natural structs

Natural structs are straightforward record objects that expose const and mutable accessors. Using the fuchsia.examples/Color FIDL type as an example:

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="natural-structs" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Natural unions

Natural unions are sum types similar to std::variant. Using the strict fuchsia.examples/JsonValue FIDL type and the flexible fuchsia.examples/FlexibleJsonValue FIDL type as examples:

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="natural-unions" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Natural tables

Natural tables are record types where every field is optional. Using the fuchsia.examples/User FIDL type as an example:

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="natural-tables" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Using wire domain objects

Wire types are the performance oriented flavor of C++ domain objects. Differing from natural types which maintain hierarchical object ownership, wire objects never own their out-of-line children. Whether a child object is stored inline or out-of-line is determined by the FIDL wire format.

Natural types may implicitly heap allocate the necessary storage. Conversely, the user has complete control over memory allocation of wire types. For example, you may allocate the elements of a FIDL vector on the stack, from a memory pool, or as part of a larger object. The wire vector type, fidl::VectorView<T>, is an unowned view type consisting of a raw pointer and a length. One may send the vector as part of a FIDL request without extra heap allocations by borrowing the elements via this type.

To distinguish from the natural types, wire types from a FIDL library are defined in the ...::wire nested namespace, e.g. fuchsia_my_library::wire.

The prevalence of unowned pointers in wire types makes them flexible but very unsafe. This tutorial will focus on the safer side of using wire types based on memory arenas. For more advanced usages involving unsafe memory borrows, refer to Memory ownership of wire domain objects.

Wire bits and enums

Because bits and enums have a very simple memory layout and do not have any out-of-line children, the wire types for FIDL bits and enums are the same as their natural type counterparts. To stay coherent with the overall namespace naming profiles, bits and enums are aliased into the fuchsia_my_library::wire nested namespace, appearing alongside wire structs, unions, and tables.

Using the fuchsia.examples/FileMode FIDL bits as an example, fuchsia_examples::wire::FileMode is a type alias of fuchsia_examples::FileMode.

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-bits" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Similarly, using the fuchsia.examples/LocationType FIDL enum as an example, fuchsia_examples::wire::LocationType is a type alias of fuchsia_examples::LocationType.

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-enums" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Wire structs

Wire structs are simple C++ structs that hold public member variables. Using the fuchsia.examples/Color FIDL type as an example:

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-structs" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Wire unions

Wire unions are sum types with a memory layout akin to a discriminator tag followed by a reference to the active member. Using the strict fuchsia.examples/JsonValue FIDL type and the flexible fuchsia.examples/FlexibleJsonValue FIDL type as examples:

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-unions" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Wire tables

Wire tables are record types where every field is optional. Differing from natural tables, wire tables do not own any member field. Copying a wire table is akin to aliasing (copying) a pointer. Similar to pointers, moving a wire table is an anti-pattern because that equates to a copy.

Because of the memory layout constraints of wire tables, one always use an associated Builder type to create new instances. Once a table is built, one may not add new members or clear existing members.

Using the fuchsia.examples/User FIDL type as an example:

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-tables" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

For more information on the bindings, see the bindings reference.

Convert between natural and wire domain objects

To streamline interoperability, you may call fidl::ToWire and fidl::ToNatural functions to convert between wire and natural domain objects. Using the fuchsia.examples/User FIDL type as an example:

Convert from natural to wire: fidl::ToWire

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="natural-to-wire" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Convert from wire to natural: fidl::ToNatural

{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/cpp/domain_objects/main.cc" region_tag="wire-to-natural" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Persist natural and wire domain objects

You may use fidl::Persist to serialize a natural or wire domain object into a byte vector, the primary use case being long term data persistence.

fidl::Unpersist deserializes and copies a sequence of bytes into some instance of natural domain object.

fidl::InplaceUnpersist deserializes a sequence of bytes into some instance of wire domain object, mutating the bytes in the process.

<<../../../widgets/_persistence.md>>