Interop with Rust-generated wasm

I'm interested in the potential for using a wasm runtime like wasmtime (or something similar) to run untrusted user code compiled from Rust. I've read some of the material in the Rust wasm book and some of the documentation for wasmtime and some other crates, and I want to understand better what is possible right now in the ecosystem.

Is it possible right now to create an API of Rust data types and functions that can be used by embedded wasm-compiled Rust?

I see that with wasmtime I can expose functions from the host to the embedded wasm module, but there doesn't seem to be a facility for translating data types between guest and host beyond the basic wasm integer types. For example, if I wanted to have a custom struct or enum as part of the host-provided API, or even a string, it doesn't look like there's an easy way to do this, and I would need to manually wire up to the linear wasm memory. Is this what the "Interface Types" proposal is for?

Meanwhile it looks like wasm-bindgen allows for defining these kind of interfaces with custom data types when interacting with JavaScript in the host. Is there an alternative for when the host is Rust? My naive understanding makes me think that should be an easier situation to deal with but maybe not :sweat_smile:

Thanks for any help anyone can give!

Spot on. And I'm pretty sure that functionally answers your entire question, presuming you understand said proposal.

Yes that makes things clear, thanks!

And just to be clear: wasm is quite young! That's why it seems like there "should" be a simpler solution — it just hasn't had time to be developed and mature. Things will certainly improve with time.

Absolutely, I hear you :slightly_smiling_face: It's wonderful the progress that's been made already. I'll follow the Interface Types proposal for more on this, thanks.

The wasmtime-wasi crate provides functions for interacting with the host machine which follow the WASI spec.

That means you can compile your Rust code with --target wasm32-wasi and it'll be able to use all the normal std mechanisms, with the benefit that the Wasi and WasiCtxBuilder types let you control which APIs the guest gets access to. So even if you don't have interface types you can still use things like std::fs::File and std::net::TcpStream to communicate between host and guest.

2 Likes

That's exactly what I ended up investigating last night :slight_smile: good to know I was on the right track.

It looks like I could make a pretty nice API for my purposes by providing a module in a shared crate that abstracts over WASI and handles de/serialization of messages via stdin/stdout. Thanks!

If you are making a shared crate and control the host, you can also provide your own host bindings and add wrappers to the shared crate which hide the implementation details.

Kinda like how std abstracts over LLVM intrinsics and OS-specific syscalls.


struct Thing(*mut intrinsics::MyInterface);

impl Thing {
  pub fn new() -> Self {
    unsafe {
      let ptr = intrinsics::my_interface_new();
      assert!(!ptr.is_null());
      Thing(ptr)
    }
  }

  pub fn get_property(&self, name: &str) -> Option<&str> { ... }
 
}

mod intrinsics {
  extern "C" {
    type MyInterface;
  
    fn my_interface_new() -> *mut MyInterface;
    fn my_interface_get_property(ptr: *const MyInterface, name: *const c_char) -> *const c_char;
    fn my_interface_free(ptr: *mut MyInterface);
  }
}

It's just not as nice as Interface Types because you need to create a C API instead of using traits.