Stream: wasmtime

Topic: Passing `InputStream` or `Pollable` from Host to Guest


view this post on Zulip Jonathan H (Aug 16 2025 at 15:55):

Hi all,

I have been struggling for some time on how to communicate from Host to Guest. Ultimately, I'd like for the Guest to be able to block on a message stream in its own thread so that the Host can send bytes/messages to the Guest. The Guest processes bytes/messages as the Host sends them.

I believe I have my wit directory figured out and am successfully generating both Host and Guest bindings. However, when I try to instantiate and call a function of the component, I am receiving errors due to mismatched types. I've tried to pare down the code to a minimal working example:

.wit:

package whisper:module@1.0.0;

world whisper-module {
    use wasi:io/streams@0.2.6.{input-stream};

    export run: func(message-stream: input-stream);
}

main.rs. Sorry it's a bit verbose

use std::error::Error;

use wasmtime::{Config, Engine, Store};
use wasmtime_wasi::{p2::{pipe::MemoryInputPipe, subscribe, IoView, WasiCtx, WasiCtxBuilder, WasiView}, DirPerms, FilePerms, ResourceTable};

wasmtime::component::bindgen!({
    world: "whisper-module",
    path: r#"..\wit"#,
});

pub struct MyState {
    ctx: WasiCtx,
    table: ResourceTable,
}

impl IoView for MyState {
    fn table(&mut self) -> &mut ResourceTable { &mut self.table }
}
impl WasiView for MyState {
    fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {

    let data = std::fs::read(r".\target\wasm32-wasip2\debug\whisper_module_file_wasm.wasm").unwrap();

    let mut config = Config::new();
    config.async_support(true);
    config.wasm_component_model(true);

    let engine = Engine::new(&config)?;
    let mut linker = wasmtime::component::Linker::<MyState>::new(&engine);
    WhisperModule::add_to_linker(&mut linker, |state: &mut MyState| state)?;

    wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;

    let mut builder = WasiCtxBuilder::new();

    let input_pipe = MemoryInputPipe::new(vec![]);

    let mut resource_table = ResourceTable::new();
    let resource = resource_table.push(input_pipe).unwrap();

    let pollable = subscribe(&mut resource_table, resource).unwrap();

    let mut store = Store::new(
        &engine,
        MyState {
            ctx: builder
                .inherit_network()
                .inherit_stdio()
                .preopened_dir(r#"C:\"#, r#"/"#, DirPerms::all(), FilePerms::all())?
                .build(),
            table: ResourceTable::new(),
        },
    );

    let component = wasmtime::component::Component::new(&engine, data)?;

    let module = WhisperModule::instantiate(&mut store, &component, &linker)?;

    module.call_run(&mut store, resource);

    Ok(())
}

The error I am receiving on the module.call_run line. I get:

mismatched types
expected struct `wasmtime::component::Resource<wasi::io::streams::InputStream>`
found struct `wasmtime::component::Resource<MemoryInputPipe>`

If I try to generalize it to a pollable, I get:

mismatched types
expected struct `wasmtime::component::Resource<wasi::io::poll::Pollable>`
   found struct `wasmtime::component::Resource<DynPollable>`

My main questions:

  1. Is this a reasonable approach? Can a Host stream bytes/messages to a Guest?
  2. How do I fix the type mismatches? I expected MemoryInputPipe to work, because it implements InputStream. Why doesn't it?

Thank you!

view this post on Zulip Jonathan H (Aug 16 2025 at 21:56):

Okay, I somehow fumbled my way through it and have a tokio::io::duplex as an AsyncReadStream passed to the Guest. I still am not quite sure how everything works and ties together, but can make progress with this I think. If there are any good resources about how wit bindings translate into rust code on both the Host side and Guest side, I would greatly appreciate any references.

Thanks everyone!


Last updated: Dec 06 2025 at 06:05 UTC