I'm writing a fairly lightweight Rust application to be compiled to WebAssembly and run in the browser. This application also includes some much heavier functionality which isn't always needed which I would like, if possible, to load on demand so as not to bog down the browser unneccessarily. This extra functionality is currently written in C++, although porting it to Rust wouldn't be too hard. My current plan is to build the light functionality and heavy functionality as two separate WASM files.
Is there a good way to call from WASM to WASM within the browser without JS as an intermediary (it's okay if both have to be Rust and same compiler version -- my Nix-based build script is more than capable of guaranteeing that), or is marshalling through JS my best bet?
I haven't tried doing this, but I guess you could try instantiating your other wasm file using js_sys::WebAssembly instantiate, and calling the other wasm's instances via instance.exports.
Edit: I tried with the following and it worked!:
use js_sys::{
Function, Object, Reflect,
WebAssembly::{self, Instance},
};
use wasm_bindgen::{JsCast, JsValue, prelude::*};
use wasm_bindgen_futures::JsFuture;
#[wasm_bindgen(main)]
async fn main() {
// other.wasm was compiled with emscripten. It only has an int add(int,int) function
let path = "./dist/other.wasm";
let fetched = web_sys::window().unwrap().fetch_with_str(path);
let wasm = JsFuture::from(WebAssembly::instantiate_streaming(&fetched, &Object::new()))
.await
.unwrap();
let instance = Reflect::get(&wasm, &"instance".into())
.unwrap()
.dyn_into::<Instance>()
.unwrap();
let add_func = Reflect::get(&instance.exports(), &"add".into())
.unwrap()
.dyn_into::<Function>()
.unwrap();
let result = add_func
.call2(&JsValue::null(), &1.into(), &2.into())
.unwrap();
web_sys::console::log_1(&result);
}
The required Rust dependencies:
[dependencies]
js-sys = "0.3"
web-sys = { version = "0.3", features = ["Window", "console"] }
wasm-bindgen = "0.2.100"
wasm-bindgen-futures = "0.4.50"
This is nice, and it'd work, but I was hoping for something that would let me pass arbitrary types across the FFI boundary without having to wrap them in a JsValue. If that's not possible with current tooling, that's OK.
Hmm. I guess what I could do (it occurs to me this is rather ill-advised) is have the callee return a pointer as a u32
and then have the caller directly read the bytes out of instance.memory
. Doing that without violating any soundness guarantees is gonna be a challenge but it sounds like the fun kind.