Macro wasmtime::component::bindgen

source ·
bindgen!() { /* proc-macro */ }
Available on crate features runtime and component-model only.
Expand description

Generate bindings for a WIT world.

This macro ingests a WIT world and will generate all the necessary bindings for instantiating components that ascribe to the world. This provides a higher-level representation of working with a component than the raw Instance type which must be manually-type-checked and manually have its imports provided via the Linker type.

The most basic usage of this macro is:

wasmtime::component::bindgen!();

This will parse your projects WIT package in a wit directory adjacent to your crate’s Cargo.toml. All of the *.wit files in that directory are parsed and then the single world found will be used for bindings.

For example if your project contained:

// wit/my-component.wit

package my:project;

world hello-world {
    import name: func() -> string;
    export greet: func();
}

Then you can interact with the generated bindings like so:

use wasmtime::component::*;
use wasmtime::{Config, Engine, Store};

bindgen!();

struct MyState {
    name: String,
}

// Imports into the world, like the `name` import for this world, are
// satisfied through traits.
impl HelloWorldImports for MyState {
    // Note the `Result` return value here where `Ok` is returned back to
    // the component and `Err` will raise a trap.
    fn name(&mut self) -> wasmtime::Result<String> {
        Ok(self.name.clone())
    }
}

fn main() -> wasmtime::Result<()> {
    // Configure an `Engine` and compile the `Component` that is being run for
    // the application.
    let mut config = Config::new();
    config.wasm_component_model(true);
    let engine = Engine::new(&config)?;
    let component = Component::from_file(&engine, "./your-component.wasm")?;

    // Instantiation of bindings always happens through a `Linker`.
    // Configuration of the linker is done through a generated `add_to_linker`
    // method on the bindings structure.
    //
    // Note that the closure provided here is a projection from `T` in
    // `Store<T>` to `&mut U` where `U` implements the `HelloWorldImports`
    // trait. In this case the `T`, `MyState`, is stored directly in the
    // structure so no projection is necessary here.
    let mut linker = Linker::new(&engine);
    HelloWorld::add_to_linker(&mut linker, |state: &mut MyState| state)?;

    // As with the core wasm API of Wasmtime instantiation occurs within a
    // `Store`. The bindings structure contains an `instantiate` method which
    // takes the store, component, and linker. This returns the `bindings`
    // structure which is an instance of `HelloWorld` and supports typed access
    // to the exports of the component.
    let mut store = Store::new(
        &engine,
        MyState {
            name: "me".to_string(),
        },
    );
    let (bindings, _) = HelloWorld::instantiate(&mut store, &component, &linker)?;

    // Here our `greet` function doesn't take any parameters for the component,
    // but in the Wasmtime embedding API the first argument is always a `Store`.
    bindings.call_greet(&mut store)?;
    Ok(())
}

The function signatures within generated traits and on generated exports match the component-model signatures as specified in the WIT world item. Note that WIT also has support for importing and exports interfaces within worlds, which can be bound here as well:

For example this WIT input

// wit/my-component.wit

package my:project;

interface host {
    gen-random-integer: func() -> u32;
    sha256: func(bytes: list<u8>) -> string;
}

world hello-world {
    import host;

    export demo: interface {
        run: func();
    }
}

Then you can interact with the generated bindings like so:

use wasmtime::component::*;
use wasmtime::{Config, Engine, Store};
use my::project::host::Host;

bindgen!();

struct MyState {
    // ...
}

// Note that the trait here is per-interface and within a submodule now.
impl Host for MyState {
    fn gen_random_integer(&mut self) -> wasmtime::Result<u32> {
        Ok(rand::thread_rng().gen())
    }

    fn sha256(&mut self, bytes: Vec<u8>) -> wasmtime::Result<String> {
        // ...
    }
}

fn main() -> wasmtime::Result<()> {
    let mut config = Config::new();
    config.wasm_component_model(true);
    let engine = Engine::new(&config)?;
    let component = Component::from_file(&engine, "./your-component.wasm")?;

    let mut linker = Linker::new(&engine);
    HelloWorld::add_to_linker(&mut linker, |state: &mut MyState| state)?;

    let mut store = Store::new(
        &engine,
        MyState { /* ... */ },
    );
    let (bindings, _) = HelloWorld::instantiate(&mut store, &component, &linker)?;

    // Note that the `demo` method returns a `&Demo` through which we can
    // run the methods on that interface.
    bindings.demo().call_run(&mut store)?;
    Ok(())
}

The generated bindings can additionally be explored more fully with cargo doc to see what types and traits and such are generated.

§Syntax

This procedural macro accepts a few different syntaxes. The primary purpose of this macro is to locate a WIT package, parse it, and then extract a world from the parsed package. There are then codegen-specific options to the bindings themselves which can additionally be specified.

Usage of this macro looks like:

// Parse the `wit/` folder adjacent to this crate's `Cargo.toml` and look
// for a single `world` in it. There must be exactly one for this to
// succeed.
bindgen!();

// Parse the `wit/` folder adjacent to this crate's `Cargo.toml` and look
// for the world `foo` contained in it.
bindgen!("foo");

// Parse the folder `other/wit/folder` adjacent to `Cargo.toml`.
bindgen!(in "other/wit/folder");
bindgen!("foo" in "other/wit/folder");

// Parse the file `foo.wit` as a single-file WIT package with no
// dependencies.
bindgen!("foo" in "foo.wit");

A more configured version of invoking this macro looks like:

bindgen!({
    world: "foo", // not needed if `path` has one `world`

    // same as in `bindgen!(in "other/wit/folder")
    path: "other/wit/folder",

    // Instead of `path` the WIT document can be provided inline if
    // desired.
    inline: "
        package my:inline;

        world foo {
            // ...
        }
    ",

    // Add calls to `tracing::span!` before each import or export is called
    // to log arguments and return values.
    //
    // This option defaults to `false`.
    tracing: true,

    // Imports will be async functions through #[async_trait] and exports
    // are also invoked as async functions. Requires `Config::async_support`
    // to be `true`.
    //
    // Note that this is only async for the host as the guest will still
    // appear as if it's invoking blocking functions.
    //
    // This option defaults to `false`.
    async: true,

    // Alternative mode of async configuration where this still implies
    // async instantiation happens, for example, but more control is
    // provided over which imports are async and which aren't.
    //
    // Note that in this mode all exports are still async.
    async: {
        // All imports are async except for functions with these names
        except_imports: ["foo", "bar"],

        // All imports are synchronous except for functions with these names
        //
        // Note that this key cannot be specified with `except_imports`,
        // only one or the other is accepted.
        only_imports: ["foo", "bar"],
    },

    // This can be used to translate WIT return values of the form
    // `result<T, error-type>` into `Result<T, RustErrorType>` in Rust.
    // Users must define `RustErrorType` and the `Host` trait for the
    // interface which defines `error-type` will have a method
    // called `convert_error_type` which converts `RustErrorType`
    // into `wasmtime::Result<ErrorType>`. This conversion can either
    // return the raw WIT error (`ErrorType` here) or a trap.
    //
    // By default this option is not specified.
    trappable_error_type: {
        "wasi:io/streams/stream-error" => RustErrorType,
    },

    // All generated bindgen types are "owned" meaning types like `String`
    // are used instead of `&str`, for example. This is the default and
    // ensures that the same type used in both imports and exports uses the
    // same generated type.
    ownership: Owning,

    // Alternative to `Owning` above where borrowed types attempt to be used
    // instead. The `duplicate_if_necessary` configures whether duplicate
    // Rust types will be generated for the same WIT type if necessary, for
    // example when a type is used both as an import and an export.
    ownership: Borrowing {
        duplicate_if_necessary: true
    },

    // Restrict the code generated to what's needed for the interface
    // imports in the inlined WIT document fragment.
    interfaces: "
        import wasi:cli/command;
    ",

    // Remap imported interfaces or resources to types defined in Rust
    // elsewhere. Using this option will prevent any code from being
    // generated for interfaces mentioned here. Resources named here will
    // not have a type generated to represent the resource.
    //
    // Interfaces mapped with this option should be previously generated
    // with an invocation of this macro. Resources need to be mapped to a
    // Rust type name.
    with: {
        // This can be used to indicate that entire interfaces have
        // bindings generated elsewhere with a path pointing to the
        // bindinges-generated module.
        "wasi:random/random": wasmtime_wasi::bindings::random::random,

        // Similarly entire packages can also be specified.
        "wasi:cli": wasmtime_wasi::bindings::cli,

        // Or, if applicable, entire namespaces can additionally be mapped.
        "wasi": wasmtime_wasi::bindings,

        // Versions are supported if multiple versions are in play:
        "wasi:http/types@0.2.0": wasmtime_wasi_http::bindings::http::types,
        "wasi:http@0.2.0": wasmtime_wasi_http::bindings::http,

        // The `with` key can also be used to specify the `T` used in
        // import bindings of `Resource<T>`. This can be done to configure
        // which typed resource shows up in generated bindings and can be
        // useful when working with the typed methods of `ResourceTable`.
        "wasi:filesystem/types/descriptor": MyDescriptorType,
    },
});