wit_bindgen

Macro generate

Source
generate!() { /* proc-macro */ }
Expand description

Generate bindings for an input WIT document.

This macro is the bread-and-butter of the wit-bindgen crate. The macro here will parse WIT as input and generate Rust bindings to work with the world that’s specified in the WIT. For a primer on WIT see this documentation and for a primer on worlds see here.

This macro takes as input a WIT package as well as a world within that package. It will then generate a Rust function for all imports into the world. If there are any exports then a Rust trait will be generated for you to implement. The macro additionally takes a number of configuration parameters documented below as well.

Basic invocation of the macro can look like:

use wit_bindgen::generate;

generate!();

This will parse a WIT package in the wit folder adjacent to your project’s Cargo.toml file. Within this WIT package there must be precisely one world and that world will be the one that has bindings generated for it. All other options remain at their default values (more on this below).

If your WIT package has more than one world, or if you want to select a world from the dependencies, you can specify a world explicitly:

use wit_bindgen::generate;

generate!("my-world");
generate!("wasi:cli/imports");

This form of the macro takes a single string as an argument which is a “world specifier” to select which world is being generated. As a single string, such as "my-world", this selects the world named my-world in the package being parsed in the wit folder. The longer form specification "wasi:cli/imports" indicates that the wasi:cli package, located in the wit/deps folder, will have a world named imports and those bindings will be generated.

If your WIT package is located in a different directory than one called wit then it can be specified with the in keyword:

use wit_bindgen::generate;

generate!(in "./my/other/path/to/wit");
generate!("a-world" in "../path/to/wit");

The full-form of the macro, however, takes a braced structure which is a “bag of options”:

use wit_bindgen::generate;

generate!({
    world: "my-world",
    path: "../path/to/wit",
    // ...
});

For documentation on each option, see below.

§Exploring generated bindings

Once bindings have been generated they can be explored via a number of means to see what was generated:

  • Using cargo doc should render all of the generated bindings in addition to the original comments in the WIT format itself.
  • If your IDE supports rust-analyzer code completion should be available to explore and see types.
  • The wit-bindgen CLI tool, packaged as wit-bindgen-cli on crates.io, can be executed the same as the generate! macro and the output can be read.
  • If you’re seeing an error, WIT_BINDGEN_DEBUG=1 can help debug what’s happening (more on this below) by emitting macro output to a file.
  • This documentation can be consulted for various constructs as well.

Currently browsing generated code may have road bumps on the way. If you run into issues or have idea of how to improve the situation please file an issue.

§Namespacing

In WIT, worlds can import and export interfaces, functions, and types. Each interface can either be “anonymous” and only named within the context of a world or it can have a “package ID” associated with it. Names in Rust take into account all the names associated with a WIT interface. For example the package ID foo:bar/baz would create a mod foo which contains a mod bar which contains a mod baz.

WIT imports and exports are additionally separated into their own namespaces. Imports are generated at the level of the generate! macro where exports are generated under an exports namespace.

§Imports

Imports into a world can be types, resources, functions, and interfaces. Each of these is bound as a Rust type, function, or module. The intent is that the WIT interfaces map to what is roughly idiomatic Rust for the given interface.

§Imports: Top-level functions and types

Imports at the top-level of a world are generated directly where the generate! macro is invoked.

use wit_bindgen::generate;

generate!({
    inline: r"
        package a:b;

        world the-world {
            record fahrenheit {
                degrees: f32,
            }

            import what-temperature-is-it: func() -> fahrenheit;

            record celsius {
                degrees: f32,
            }

            import convert-to-celsius: func(a: fahrenheit) -> celsius;
        }
    ",
});

fn test() {
    let current_temp = what_temperature_is_it();
    println!("current temp in fahrenheit is {}", current_temp.degrees);
    let in_celsius: Celsius = convert_to_celsius(current_temp);
    println!("current temp in celsius is {}", in_celsius.degrees);
}

§Imports: Interfaces

Interfaces are placed into submodules where the generate! macro is invoked and are namespaced based on their identifiers.

use wit_bindgen::generate;

generate!({
    inline: r"
        package my:test;

        interface logging {
            enum level {
                debug,
                info,
                error,
            }
            log: func(level: level, msg: string);
        }

        world the-world {
            import logging;
            import global-logger: interface {
                use logging.{level};

                set-current-level: func(level: level);
                get-current-level: func() -> level;
            }
        }
    ",
});

// `my` and `test` are from `package my:test;` and `logging` is for the
// interfac name.
use my::test::logging::Level;

fn test() {
    let current_level = global_logger::get_current_level();
    println!("current logging level is {current_level:?}");
    global_logger::set_current_level(Level::Error);

    my::test::logging::log(Level::Info, "Hello there!");
}

§Imports: Resources

Imported resources generate a type named after the name of the resource. This type is then used both for borrows as &T as well as via ownership as T. Resource methods are bound as methods on the type T.

use wit_bindgen::generate;

generate!({
    inline: r#"
        package my:test;

        interface logger {
            enum level {
                debug,
                info,
                error,
            }

            resource logger {
                constructor(destination: string);
                log: func(level: level, msg: string);
            }
        }

        // Note that while this world does not textually import the above
        // `logger` interface it is a transitive dependency via the `use`
        // statement so the "elaborated world" imports the logger.
        world the-world {
            use logger.{logger};

            import get-global-logger: func() -> logger;
        }
    "#,
});

use my::test::logger::Level;

fn test() {
    let logger = get_global_logger();
    logger.log(Level::Debug, "This is a global message");

    let logger2 = Logger::new("/tmp/other.log");
    logger2.log(Level::Info, "This is not a global message");
}

Note in the above example the lack of import of Logger. The use statement imported the Logger type, an alias of it, from the logger interface into the-world. This generated a Rust type alias so Logger was available at the top-level.

§Exports: Basic Usage

A WIT world can not only import functionality but can additionally export functionality as well. An export represents a contract that the Rust program must implement to be able to work correctly. The generate! macro’s goal is to take care of all the low-level and ABI details for you, so the end result is that generate!, for exports, will generate Rust traits that you must implement.

A minimal example of this is:

use wit_bindgen::generate;

generate!({
    inline: r#"
        package my:test;

        world my-world {
            export hello: func();
        }
    "#,
});

struct MyComponent;

impl Guest for MyComponent {
    fn hello() {}
}

export!(MyComponent);

Here the Guest trait was generated by the generate! macro and represents the functions at the top-level of my-world, in this case the function hello. A custom type, here called MyComponent, is created and the trait is implemented for that type.

Additionally a macro is generated by generate! (macros generating macros) called export!. The export! macro is given a component that implements the export traits and then it will itself generate all necessary #[no_mangle] functions to implement the ABI required.

§Exports: Multiple Interfaces

Each interface in WIT will generate a trait that must be implemented in addition to the top-level trait for the world. All traits are named Guest here and are namespaced appropriately in modules:

use wit_bindgen::generate;

generate!({
    inline: r#"
        package my:test;

        interface a {
            func-in-a: func();
            second-func-in-a: func();
        }

        world my-world {
            export a;
            export b: interface {
                func-in-b: func();
            }
            export c: func();
        }
    "#,
});

struct MyComponent;

impl Guest for MyComponent {
    fn c() {}
}

impl exports::my::test::a::Guest for MyComponent {
    fn func_in_a() {}
    fn second_func_in_a() {}
}

impl exports::b::Guest for MyComponent {
    fn func_in_b() {}
}

export!(MyComponent);

Here note that there were three Guest traits generated for each of the three groups: two interfaces and one world. Also note that traits (and types) for exports are namespaced in an exports module.

Note that when the top-level world does not have any exported functions, or if an interface does not have any functions, then no trait is generated:

use wit_bindgen::generate;

generate!({
    inline: r#"
        package my:test;

        interface a {
            type my-type = u32;
        }

        world my-world {
            export b: interface {
                use a.{my-type};

                foo: func() -> my-type;
            }
        }
    "#,
});

struct MyComponent;

impl exports::b::Guest for MyComponent {
    fn foo() -> u32 {
        42
    }
}

export!(MyComponent);

§Exports: Resources

Exporting a resource is significantly different than importing a resource. A component defining a resource can create new resources of that type at any time, for example. Additionally resources can be “dereferenced” into their underlying values within the component.

Owned resources have a custom type generated and borrowed resources are generated with a type of the same name suffixed with Borrow<'_>, such as MyResource and MyResourceBorrow<'_>.

Like interfaces the methods and functions used with a resource are packaged up into a trait.

Specifying a custom resource type is done with an associated type on the corresponding trait for the resource’s containing interface/world:

use wit_bindgen::generate;
use std::cell::{RefCell, Cell};

generate!({
    inline: r#"
        package my:test;

        interface logging {
            enum level {
                debug,
                info,
                error,
            }

            resource logger {
                constructor(level: level);
                log: func(level: level, msg: string);
                level: func() -> level;
                set-level: func(level: level);
            }
        }

        world my-world {
            export logging;
        }
    "#,
});

use exports::my::test::logging::{Guest, GuestLogger, Level};

struct MyComponent;

// Note that the `logging` interface has no methods of its own but a trait
// is required to be implemented here to specify the type of `Logger`.
impl Guest for MyComponent {
    type Logger = MyLogger;
}

struct MyLogger {
    level: Cell<Level>,
    contents: RefCell<String>,
}

impl GuestLogger for MyLogger {
    fn new(level: Level) -> MyLogger {
        MyLogger {
            level: Cell::new(level),
            contents: RefCell::new(String::new()),
        }
    }

    fn log(&self, level: Level, msg: String) {
        if level as u32 <= self.level.get() as u32 {
            self.contents.borrow_mut().push_str(&msg);
            self.contents.borrow_mut().push_str("\n");
        }
    }

    fn level(&self) -> Level {
        self.level.get()
    }

    fn set_level(&self, level: Level) {
        self.level.set(level);
    }
}

export!(MyComponent);

It’s important to note that resources in Rust do not get &mut self as methods, but instead are required to be defined with &self. This requires the use of interior mutability such as Cell and RefCell above from the std::cell module.

§Exports: The export! macro

Components are created by having exported WebAssembly functions with specific names, and these functions are not created when generate! is invoked. Instead these functions are created afterwards once you’ve defined your own type an implemented the various traits for it. The #[no_mangle] functions that will become the component are created with the generated export! macro.

Each call to generate! will itself generate a macro called export!. The macro’s first argument is the name of a type that implements the traits generated:

use wit_bindgen::generate;

generate!({
    inline: r#"
        package my:test;

        world my-world {
            // ...
        }
    "#,
});

struct MyComponent;

impl Guest for MyComponent {
    // ...
}

export!(MyComponent);

This argument is a Rust type which implements the Guest traits generated by generate!. Note that all Guest traits must be implemented for the type provided or an error will be generated.

This macro additionally accepts a second argument. The macro itself needs to be able to find the module where the generate! macro itself was originally invoked. Currently that can’t be done automatically so a path to where generate! was provided can also be passed to the macro. By default, the argument is set to self:

use wit_bindgen::generate;

generate!({
    // ...
});
export!(MyComponent with_types_in self);

This indicates that the current module, referred to with self, is the one which had the generate! macro expanded.

If, however, the generate! macro was run in a different module then that must be configured:

mod bindings {
    wit_bindgen::generate!({
        // ...
    });
}
bindings::export!(MyComponent with_types_in bindings);

§Debugging output to generate!

While wit-bindgen is tested to the best of our ability there are inevitably bugs and issues that arise. These can range from bad error messages to misconfigured invocations to bugs in the macro itself. To assist with debugging these situations the macro recognizes an environment variable:

export WIT_BINDGEN_DEBUG=1

When set the macro will emit the result of expansion to a file and then include! that file. Any error messages generated by rustc should then point to the generated file and allow you to open it up, read it, and inspect it. This can often provide better context to the error than rustc provides by default with macros.

It is not recommended to set this environment variable by default as it will cause excessive rebuilds of Cargo projects. It’s recommended to only use it as necessary to debug issues.

§Options to generate!

The full list of options that can be passed to the generate! macro are as follows. Note that there are no required options, they all have default values.

use wit_bindgen::generate;

generate!({
    // The name of the world that bindings are being generated for. If this
    // is not specified then it's required that the package selected
    // below has a single `world` in it.
    world: "my-world",

    // Path to parse WIT and its dependencies from. Defaults to the `wit`
    // folder adjacent to your `Cargo.toml`.
    //
    // This parameter also supports the form of a list, such as:
    // ["../path/to/wit1", "../path/to/wit2"]
    // Usually used in testing, our test suite may want to generate code
    // from wit files located in multiple paths within a single mod, and we
    // don't want to copy these files again.
    path: "../path/to/wit",

    // Enables passing "inline WIT". If specified this is the default
    // package that a world is selected from. Any dependencies that this
    // inline WIT refers to must be defined in the `path` option above.
    //
    // By default this is not specified.
    inline: "
        world my-world {
            import wasi:cli/imports;

            export my-run: func()
        }
    ",

    // Additional traits to derive for all defined types. Note that not all
    // types may be able to implement these traits, such as resources.
    //
    // By default this set is empty.
    additional_derives: [PartialEq, Eq, Hash, Clone],

    // When generating bindings for interfaces that are not defined in the
    // same package as `world`, this option can be used to either generate
    // those bindings or point to already generated bindings.
    // For example, if your world refers to WASI types then the `wasi` crate
    // already has generated bindings for all WASI types and structures. In this
    // situation the key `with` here can be used to use those types
    // elsewhere rather than regenerating types.
    //
    // If, however, your world refers to interfaces for which you don't have
    // already generated bindings then you can use the special `generate` value
    // to have those bindings generated.
    //
    // The `with` key only supports replacing types at the interface level
    // at this time.
    //
    // When an interface is specified no bindings will be generated at
    // all. It's assumed bindings are fully generated somewhere else. This is an
    // indicator that any further references to types defined in these
    // interfaces should use the upstream paths specified here instead.
    //
    // Any unused keys in this map are considered an error.
    with: {
        "wasi:io/poll": wasi::io::poll,
        "some:package/my-interface": generate,
    },

    // Indicates that all interfaces not present in `with` should be assumed
    // to be marked with `generate`.
    generate_all,

    // An optional list of function names to skip generating bindings for.
    // This is only applicable to imports and the name specified is the name
    // of the function.
    skip: ["foo", "bar", "baz"],

    // Configuration of how Rust types are generated.
    //
    // This option will change how WIT types are mapped to Rust types. There
    // are a number of ways this can be done depending on the context. For
    // example a Rust `&str` is suitable to pass to an imported function but
    // an exported function receives a `String`. These both represent the
    // WIT type `string`, however.
    //
    // Type generation becomes extra-significant when aggregates come into
    // play (such as a WIT `record` or `variant`), especially when the
    // aggregate is used both in an imported function and exported one.
    //
    // There are three modes of ownership, documented here, but only one
    // can be specified.
    //
    // The default mode is "Owning" meaning that all Rust types will by
    // default contain their owned containers. For example a `record` with
    // a `string` will map to a Rust `struct` containing a `String`. This
    // maximizes the chance that types can be shared between imports and
    // exports but can come at a cost where calling an import may require
    // more allocations than necessary.
    ownership: Owning,

    // Specifies an alternative name for the `export!` macro generated for
    // any exports this world has.
    //
    // Defaults to "export"
    export_macro_name: "export",

    // Indicates whether the `export!` macro is `pub` or just `pub(crate)`.
    //
    // This defaults to `false`.
    pub_export_macro: false,

    // The second mode of ownership is "Borrowing". This mode then
    // additionally has a boolean flag indicating whether duplicate types
    // should be generated if necessary.
    //
    // This mode will prefer using borrowed values in Rust to represent WIT
    // values where possible. For example if the argument to an imported
    // function is a record-with-a-string then in Rust that will generate a
    // `struct` with a lifetime parameter storing `&'a str`.
    //
    // The `duplicate_if_necessary` flag will cause duplicate types to be
    // generated when a WIT type is used both in an import and export. In
    // this situation one will be called `FooParam` and one will be called
    // `FooResult` (where `foo` is the WIT name).
    //
    // It's generally recommended to not turn this on unless performance
    // requires it. Even if so, please feel free to open an issue on the
    // `wit-bindgen` repository to help improve the default "Owning" use
    // case above if possible.
    ownership: Borrowing { duplicate_if_necessary: false },

    // The generated `export!` macro, if any, will by default look for
    // generated types adjacent to where the `export!` macro is invoked
    // through the `self` module. This option can be used to change the
    // defaults to look somewhere else instead.
    default_bindings_module: "path::to::bindings",

    // This will suffix the custom section containing component type
    // information with the specified string. This is not required by
    // default but if the same world is generated in two different locations
    // in the crate then one bindings generation location will need this
    // suffix to avoid having the custom sections corrupt each other.
    type_section_suffix: "suffix",

    // Configures the path to the `wit-bindgen` crate itself. By default
    // this is `wit_bindgen` assuming that your crate depends on the
    // `wit-bindgen` crate itself.
    runtime_path: "path::to::wit_bindgen",

    // Configure where the `bitflags` crate is located. By default this
    // is `wit_bindgen::bitflags` which already reexports `bitflags` for
    // you.
    bitflags_path: "path::to::bitflags",

    // Indicates that instead of `&str` and `String` the `&[u8]` and
    // `Vec<u8>` types should be used. Only intended for cases where
    // compiled size is of the utmost concern as this can avoid pulling in
    // UTF-8 validation.
    raw_strings,

    // Emits `#[cfg(feature = "std")]` around `impl Error for ... {}` blocks
    // for generated types. This is a niche option that is only here to
    // support the standard library itself depending on this crate one day.
    std_feature,

    // Disable a workaround to force wasm constructors to be run only once
    // when exported functions are called.
    disable_run_ctors_once_workaround: false,

    // Whether to generate unused `record`, `enum`, `variant` types.
    // By default, they will not be generated unless they are used as input
    // or return value of a function.
    generate_unused_types: false,

    // A list of "features" which correspond to WIT features to activate
    // when parsing WIT files. This enables `@unstable` annotations showing
    // up and having bindings generated for them.
    //
    // By default this is an empty list.
    features: ["foo", "bar", "baz"],

    // Disables generation of a `#[used]` static to try harder to get the
    // custom section describing WIT types linked into the binary when
    // used in library-like situations. This is `false` by default with
    // `#[used]` statics being emitted.
    disable_custom_section_link_helpers: false,
});