Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Define host functions for WebAssembly

wasmtime-badge cat-wasm-badge

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!