Available on crate feature
component-model and crate feature runtime and (docsrs) only.Expand description
Example of generating async bindings for imported resources in a world.
Notable differences from _4_imported_resources are:
- async functions are used
- enabled async in bindgen! macro
- flags configured for individual resource functions
*_asyncentry points are required, such asinstantiate_async, awaitingcall_*functions, andResourceAny::resource_drop_async
See wasi_async_example for async function calls on a host.
use wasmtime::{
Config, Engine, Result, Store,
component::{bindgen, Component, HasSelf, Linker, ResourceTable},
};
bindgen!({
inline: r#"
package example:resource-async;
interface logging {
enum level {
debug,
info,
warn,
error,
}
resource logger {
constructor(max-level: level);
get-max-level: func() -> level;
set-max-level: func(level: level);
/// May block if the host's log buffer is full (i.e. backpressure).
log: async func(level: level, msg: string);
}
}
world app {
import logging;
/// Because `logging.logger.log` is async, `run` must also be async; otherwise,
/// a trap will occur at runtime if `run` calls `log` and it blocks.
export run: async func();
}
"#,
imports: {
// This is the notation that's used to flag individual methods:
//
// "example:resource-async/logging.[constructor]logger": trappable,
// "example:resource-async/logging.[method]logger.get-max-level": trappable,
// "example:resource-async/logging.[method]logger.set-max-level": trappable,
// "example:resource-async/logging.[method]logger.log": trappable,
//
// Note that if one resource method specifies a flag, the remaining resource methods
// are no longer covered by e.g. `default: trappable`, and must be flagged
// individually.
default: trappable,
},
with: {
// Specify that our host resource is going to point to the `MyLogger`
// which is defined just below this macro.
"example:resource-async/logging.logger": MyLogger,
},
});
/// A sample host-defined type which contains arbitrary host-defined data.
///
/// In this case this is relatively simple but there's no restrictions on what
/// this type can hold other than that it must be `'static + Send`.
pub struct MyLogger {
pub max_level: example::resource_async::logging::Level,
}
use self::example::resource_async::logging::{self as bindings, Level};
#[derive(Default)]
struct State {
// Manages the mapping of `MyLogger` structures to `Resource<MyLogger>`.
table: ResourceTable,
}
// This separate `HostLogger` trait serves to act as a namespace for just
// the `logger`-related resource methods.
impl bindings::HostLogger for State {
fn drop(
&mut self,
rep: wasmtime::component::Resource<bindings::Logger>,
) -> wasmtime::Result<()> {
self.table.delete(rep)?;
// ... custom destruction logic here if necessary, otherwise
// a `Drop for MyLogger` would also work.
Ok(())
}
fn get_max_level(
&mut self,
self_: wasmtime::component::Resource<MyLogger>,
) -> wasmtime::Result<bindings::Level> {
let logger = self.table.get(&self_)?;
Ok(logger.max_level)
}
// A `constructor` in WIT maps to a `new` function in Rust.
fn new(
&mut self,
max_level: bindings::Level,
) -> wasmtime::Result<wasmtime::component::Resource<bindings::Logger>> {
let id = self.table.push(MyLogger { max_level })?;
Ok(id)
}
fn set_max_level(
&mut self,
self_: wasmtime::component::Resource<MyLogger>,
level: bindings::Level,
) -> wasmtime::Result<()> {
let logger = self.table.get_mut(&self_)?;
logger.max_level = level;
Ok(())
}
}
impl<T> bindings::HostLoggerWithStore<T> for HasSelf<State> {
async fn log(
accessor: &wasmtime::component::Accessor<T, Self>,
self_: wasmtime::component::Resource<MyLogger>,
level: Level,
msg: String,
) -> wasmtime::Result<()> {
// Do something that blocks
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
accessor.with(|mut access| {
let state = access.get();
let logger = state.table.get(&self_)?;
if (level as u32) <= (logger.max_level as u32) {
println!("{msg}");
}
Ok(())
})
}
}
// There are no free-functions on `interface logging`, so this is an empty
// impl that satisfies the trait bounds required by the linker.
impl bindings::Host for State {}
#[tokio::main]
async fn main() -> Result<()> {
let mut config = Config::new();
config.wasm_component_model_async(true);
let engine = Engine::new(&config)?;
let component = Component::from_file(&engine, "/path/to/component")?;
let mut store = Store::new(&engine, State::default());
let mut linker = Linker::<State>::new(&engine);
bindings::add_to_linker::<_, HasSelf<State>>(&mut linker, |state| state)?;
// If you try to use the `App::instantiate` method, you'll get a runtime error that
// looks something like:
// "Error: store configuration requires that `*_async` functions are used instead"
let bindings = App::instantiate_async(&mut store, &component, &linker).await?;
let bindings = crate::App::instantiate_async(&mut store, &component, &linker).await?;
store
.run_concurrent(async |accessor| bindings.call_run(&accessor).await)
.await??;
Ok(())
}Modules§
Structs§
- App
- Auto-generated bindings for an instance a component which
implements the world
app. - AppIndices
- Auto-generated bindings for index of the exports of
app. - AppPre
- Auto-generated bindings for a pre-instantiated version of a
component which implements the world
app. - MyLogger
- A sample host-defined type which contains arbitrary host-defined data.