Component Model — typed strings and structs
Core WebAssembly function parameters are limited to i32, i64, f32, and
f64. The WebAssembly Component Model removes that restriction by
adding a language-neutral interface description language called
WIT. Strings, lists, records, and variants all become first-class
types that cross the guest/host boundary without manual pointer arithmetic.
This recipe requires two extra tools:
cargo install cargo-component
rustup target add wasm32-wasip2
Define the interface in WIT
A WIT world declares what a component exports and imports. This one exports a
single greet function that takes and returns a string.
package example:greeter@0.1.0;
world greeter {
export greet: func(name: string) -> string;
}
Implement the guest
wit_bindgen::generate! reads the WIT file at compile
time and emits a Guest trait. The concrete type Component implements it,
and export! wires it up as the component’s entry point.
wit_bindgen::generate!({ world: "greeter" });
struct Component;
impl Guest for Component {
fn greet(name: String) -> String {
format!("Hello, {name}!")
}
}
export!(Component);
Build the guest into a .wasm component:
cd crates/wasm_component_guest
cargo component build --release --target wasm32-wasip2
Run it from the host
The host uses wasmtime::component::bindgen! — the same WIT
file, read at compile time — to generate a typed Greeter wrapper. Config
enables the component model, wasmtime_wasi::p2::add_to_linker_sync wires
up WASI Preview 2 (required even for guests that don’t use WASI directly), and
Greeter::instantiate produces a handle whose call_greet method accepts a
&str and returns a String.
use anyhow::Result;
use wasmtime::component::{bindgen, Component, Linker, ResourceTable};
use wasmtime::{Config, Engine, Store};
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
bindgen!({
world: "greeter",
path: "../wasm_component_guest/wit/world.wit",
});
struct State {
table: ResourceTable,
wasi: WasiCtx,
}
impl WasiView for State {
fn ctx(&mut self) -> WasiCtxView<'_> {
WasiCtxView {
ctx: &mut self.wasi,
table: &mut self.table,
}
}
}
fn main() -> Result<()> {
let mut config = Config::new();
config.wasm_component_model(true);
let engine = Engine::new(&config)?;
let component = Component::from_file(
&engine,
"crates/wasm_component_guest/target/wasm32-wasip2/release/greeter.wasm",
)?;
let mut linker: Linker<State> = Linker::new(&engine);
wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?;
let wasi = WasiCtxBuilder::new().build();
let mut store = Store::new(&engine, State {
table: ResourceTable::new(),
wasi,
});
let greeter = Greeter::instantiate(&mut store, &component, &linker)?;
let message = greeter.call_greet(&mut store, "WebAssembly")?;
println!("{message}");
Ok(())
}
Run from the repo root (after building the guest):
cargo run --manifest-path crates/wasm_component_host/Cargo.toml
# Hello, WebAssembly!