Stream: wasmtime

Topic: Implementing a Resource in a WASM Host


view this post on Zulip Utilize3214 (Feb 26 2025 at 20:42):

I'm developing a proof-of-concept to understand how to implement a resource (specified in a .wit file for a WASM module) in a Wasmtime host.

My setup:

My ultimate goal is to enable nested JSON (objects and arrays) in the interface between the host and WASM module, as suggested in this Component Model discussion.

Current Implementation

I've created a JSON structure in the host:

// Define the json variant
enum Json {
    Null, Boolean(bool),
    Number(f64),
    String(String),
    Array(Vec<Resource<LazyJson>>),
    Object(Vec<(String, Resource<LazyJson>)>),
}

// Implement the LazyJson resource
struct LazyJson;
impl LazyJson {
    fn get(&self) -> Json {
        println!("LazyJson::get called!");
        Json::String("dummy value".to_string())
    }
}

I've set up the linker and store:

let mut linker = Linker::new(&engine);  // Using wasmtime::runtime::component::linker
wasmtime_wasi::add_to_linker_sync(&mut linker)?;
let mut store = Store::new(
    &engine,
    MyState {
        ctx: WasiCtxBuilder::new().build(),
        table: ResourceTable::new(),
        lazy_json: LazyJson,
    },
);

Where I'm Stuck

I'm struggling with how to add the resource to the linker. I believe I need to do something like:

linker.root().resource("lazy-json", ResourceType, dtor);

But I'm uncertain how to specify the ResourceType and dtor (destructor).

After adding the resource to the linker, I need to wrap the function, perhaps something like:

linker.root().func_wrap("lazy-json", "get", ???)

Here I'm unsure what the arguments should be.

Question

Are there any examples demonstrating how to in a host implement and register a resource defined in .wit for a WASM module using Wasmtime and the Component Model? I'm looking for guidance on:

  1. How to properly define the resource type
  2. How to create a destructor for the resource
  3. How to correctly wrap and expose the resource functions to the WASM module

Even a simple "hello world" example would be helpful. Thanks!

There are quite a few cases that require defining recursive values: package wit:json@0.1.0; interface types { variant json { null, boolean(bool), number(float64), string(string), array(list<json>),...

view this post on Zulip Alex Crichton (Feb 26 2025 at 23:38):

Hello!

Currently there's not great documentation for this, and the "best" location is to look around at the test in the tests/all/component_model/*.rs. The reason for this is that it's expected that hosts primarily use WIT and generated bindings (which looks like this). Given though that the guest is using wit-bindgen is there a reason to not use the same WIT to generate bindings on the host? The end-result of bindgen is an add_to_linker function and you can still add, for example, WASI to the linker so the WIT files doesn't have to be an exhaustive definition of the world you're using, just a partial enough piece for the JSON wrapper.

In addition to some answers below you can also explore the generated code of wasmtime::component::bindgen! by using cargo expand too (a few more words on debugging here too)

How to properly define the resource type

You're right that [Linker::resource]https://docs.rs/wasmtime/latest/wasmtime/component/struct.LinkerInstance.html#method.resource) is what you want. You'll use ResourceType::host::<LazyJson>() here.

How to create a destructor for the resource

This is up to you. A resource is a u32 on the host and you get to decide what to do with that u32. Most of the time though it's a ResourceTable where the destructor is removing the index from the table.

How to correctly wrap and expose the resource functions to the WASM module

The function you'll define has type fn(StoreContextMut<'_, T>, Resource<LazyJson>) -> wasmtime::Result<Json>. Defining the Json type is possible with #[derive(ComponentType)] but I'd recommend using bindgen! instead as it'll be far less error-prone.


Last updated: Feb 27 2025 at 23:03 UTC