Stream: wit-bindgen

Topic: Failing to compile guest resource that has async methods


view this post on Zulip mainrs (May 29 2025 at 17:55):

My wit definition file is shared between guest and host and looks like this:

interface abi {
    resource plugin {
       constructor();

       call-me: func() -> list<string>;
    }
}

world platform {
    include wasi:keyvalue/imports@0.2.0-draft;
    export abi;
}

The keyvalue include is from the wasi-keyvalue repository and I use wit-deps to download the 0.2.0 draft tag.

My component implements the resource and provides it to the host. It has to do some networking and I wanted to re-use the same Client instance, therefore I converted my original plugin interface to a resource.

I am building the code using cargo build --target wasm32-wasip1 and wanted to use wasm-tools component new to lift it into a p2 component. From my understanding, I cannot simply use cargo build, since the keyvalue include would mean that I have to provide an implementation at compile time. And cargo component lets me link the missing functions at runtime using wasmtime.

I get this error and I do not understand why this happens:

wasm-tools component new --skip-validation \
      target/wasm32-wasip1/release/plugin.wasm -o plugin.wasm --adapt wasi_snapshot_pre
view1.reactor.wasm
error: failed to encode a component from module

Caused by:
    0: failed to decode world from module
    1: module was not valid
    2: failed to resolve import `$root::[subtask-cancel]`
    3: no top-level imported function `[subtask-cancel]` specified

This is how the guest's lib.rs file:

use exports::ugl::plugins::abi::{Guest, GuestPlugin};
use myhttpclient::MyHttpClient;
use wasi::keyvalue::{self, store::Bucket};
use wit_bindgen::generate;

generate!({
    world: "platform",
    path: "../wit",
    async: true,
    with: {
        // These are host-provided.
        "wasi:keyvalue/atomics@0.2.0-draft": generate,
        "wasi:keyvalue/batch@0.2.0-draft": generate,
        "wasi:keyvalue/store@0.2.0-draft": generate,
    },
});

struct Component;
export!(Component);

impl Guest for Component {
    type Plugin = Plugin;
}

pub struct Plugin {
    client: MyHttpClient,
    kv: Bucket,
}

impl GuestPlugin for Plugin {
    #[doc = " Creates a new instance of the plugin."]
    #[allow(async_fn_in_trait)]
    async fn new() -> Self {
        Self {
            client: MyHttpClient::new(),
            // SAFETY: plugins can expect to have a key-value store available.
            kv: keyvalue::store::open("default".into())
                .await
                .unwrap_or_else(|_| panic!("Failed to open default key-value store for plugin")),
        }
    }

    #[allow(async_fn_in_trait)]
    async fn call_me(&self) -> Vec<String> {
        // let result = self.client.get(...);
        todo!()
    }
}

Maybe I am understanding something wrong. I would appreciate any kind of help and guidance on this matter. Thank you very much!

view this post on Zulip mainrs (May 29 2025 at 21:06):

I followed this guide by Fermyon who managed to use async on the guest side in spin:
https://www.fermyon.com/blog/looking-ahead-to-wasip3

But the same dependencies fail for me.

A look at Preview 3 (WASIp3) the next major release of WASI - which brings composable concurrency to Wasm components.

view this post on Zulip mainrs (May 29 2025 at 21:43):

I found the difference! I was building my wasm binary using cargo build -p plugin --target wasm32-wasip1. Using cargo build --manifest-path plugin/Cargo.toml --target wasm32-wasip1 worked without issues.

Does anyone know why???

view this post on Zulip Lann Martin (May 29 2025 at 21:57):

Is there a plugin/.cargo directory? I'm not sure if cargo will look there when using --manifest-path. Though...the only thing relevant I would expect there would be target = "wasm-waspi1" which you already have covered... :thinking:

view this post on Zulip Lann Martin (May 29 2025 at 22:01):

Maybe something about wit_bindgen::generate!'s handling of relative paths? :shrug:


Last updated: Dec 06 2025 at 07:03 UTC