Define host functions for WebAssembly
WebAssembly modules can import functions from the host, enabling guests to
call back into Rust. This recipe defines a print function in the host that
the WASM guest calls to write a message stored in its own linear memory.
Func::wrap creates a host-defined function from a Rust closure.
The first parameter is a Caller — a handle to the calling
instance’s store. The guest module declares the import with
(import "host" "print" ...) and the host passes [print_fn.into()] as the
imports slice to Instance::new.
Instance::new takes &[Extern] — the enum that can hold a
Func, Memory, Global, or Table. .into() converts the
Func into an Extern so the slice type is satisfied.
Inside the closure, Caller::get_export retrieves the guest’s
memory export so the host can read the bytes the guest passed by pointer and
length.
use anyhow::Result;
use wasmtime::{Caller, Engine, Extern, Func, Instance, Module, Store};
fn main() -> Result<()> {
let engine = Engine::default();
let module = Module::new(
&engine,
r#"
(module
(import "host" "print" (func $print (param i32 i32)))
(memory (export "memory") 1)
(data (i32.const 0) "Hello from WebAssembly!")
(func (export "run")
(call $print (i32.const 0) (i32.const 23)))
)
"#,
)?;
let mut store = Store::new(&engine, ());
let print_fn = Func::wrap(
&mut store,
|mut caller: Caller<'_, ()>, ptr: i32, len: i32| {
if let Some(Extern::Memory(mem)) = caller.get_export("memory") {
let data = mem.data(&caller);
let bytes = &data[ptr as usize..(ptr + len) as usize];
if let Ok(s) = std::str::from_utf8(bytes) {
println!("[wasm] {s}");
}
}
},
);
let imports = [print_fn.into()];
let instance = Instance::new(&mut store, &module, &imports)?;
let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
run.call(&mut store, ())?;
Ok(())
}
Run it:
cargo run --manifest-path crates/wasm/Cargo.toml --bin host_fn
# [wasm] Hello from WebAssembly!