The comments on closure.rs are pretty great, however I can't make it work for returning a closure from a WebAssembly library.
I have a function like this:
#[wasm_bindgen]
pub fn start_game(
start_time: f64,
screen_width: f32,
screen_height: f32,
on_render: &js_sys::Function,
on_collision: &js_sys::Function,
) -> ClosureTypeHere {
// ...
}
Inside that function I make a closure, assuming Closure::wrap
is one piece of the puzzle, and copying from closure.rs):
let cb = Closure::wrap(Box::new(move |time| time * 4.2) as Box<FnMut(f64) -> f64>);
How do I return this callback from start_game
and what should ClosureTypeHere
be?
The idea is that start_game
will create local mutable objects - like a camera, and the JavaScript side should be able to call the function Rust returns in order to update that camera.
This is a good question, and one that has some nuance too! It's worth calling out the closures example in the
wasm-bindgen
guide (and the section about passing closures to JavaScript) as well, and it'd be good to contribute back to that as well if necessary!To get you started, though, you can do something like this:
Above the return value is just a plain-old JS object (here as a
JsValue
) and we create that with theClosure
type you've seen already. This will allow you to quickly return a closure to JS and you'll be able to call it from JS as well.You've also asked about storing mutable objects and such, and that can all be done through normal Rust closures, capturing, etc. For example the declaration of
FnMut(f64) -> f64
above is the signature of the JS function, and that can be any set of types such asFnMut(String, MyCustomWasmBindgenType, f64) -> Vec<u8>
if you really want. For capturing local objects you can do:(or something like that)
Here the
camera
andstate
variables will be owned by the closure and dropped at the same time. More info about just closures can be found in the Rust book.It's also worth briefly covering the memory management aspect here. In the example above we're calling
forget()
which leaks memory and can be a problem if the Rust function is called many times (as it would leak a lot of memory). The fundamental problem here is that there's memory allocated on the WASM heap which the created JS function object references. This allocated memory in theory needs to be deallocated whenever the JS function object is GC'd, but we have no way of knowing when that happens (untilWeakRef
exists!).In the meantime we've chosen an alternate strategy. The associated memory is deallocated whenever the
Closure
type itself is dropped, providing deterministic destruction. This, however, makes it difficult to work with as we need to figure out manually when to drop theClosure
. Ifforget
doesn't work for your use case, some ideas for dropping theClosure
are:First, if it's a JS closure only invoked once, then you can use
Rc
/RefCell
to drop theClosure
inside the the closure itself (using some interior mutability shenanigans). We should also eventually provide native support forFnOnce
inwasm-bindgen
as well!Next, you can return an auxiliary JS object to Rust which has a manual
free
method. For example a#[wasm_bindgen]
-annotated wrapper. This wrapper would then need to be manually freed in JS when appropriate.If you can get by,
forget
is by far the easiest thing to do for now, but this is definitely a pain point! We can't wait forWeakRef
to exist :)