What's a better way to deal with closures in WebAssembly with Rust instead of using forget and leaking memory?
Asked Answered
F

3

11

When providing callbacks to JavaScript using Closures, what's a better way to deal with avoiding freeing them? The wasm-bindgen guide suggests using .forget, but admits that that is essentially leaking memory.

Normally we'd store the handle to later get dropped at an appropriate time but for now we want it to be a global handler so we use the forget method to drop it without invalidating the closure. Note that this is leaking memory in Rust, so this should be done judiciously!

It hints at storing the closure until a time when it's appropriate to be dropped. In alexcrichton's answer to a previous question, he mentions...

[...] if it's [...] only invoked once, then you can use Rc/RefCell to drop the Closure inside the the closure itself (using some interior mutability shenanigans)

But he doesn't provide an example of this method.

The Closure documentation also gives an example of returning the reference to the closure to the JavaScript context to let it handle when to free the reference.

If we were to drop cb here it would cause an exception to be raised whenever the interval elapses. Instead we return our handle back to JS so JS can decide when to cancel the interval and deallocate the closure.

I'd also imagine there are ways to use features like lifetimes or the #[wasm_bindgen] macro on a public function to avoid this issue as well, but I'm having trouble figuring out how to do it that way.

My question is, what are the alternatives to using .forget with closures that are being passed back to JavaScript from Rust, and can I please see some simple examples of each option in use?

Forsythia answered 21/1, 2020 at 7:12 Comment(0)
O
1

I recently built a small commercial app and was stuck on this for weeks and was really excited when I got this working. I ended up using Closure.once_into_js. However, that also has the caveat that "The only way the FnOnce is deallocated is by calling the JavaScript function. If the JavaScript function is never called then the FnOnce and everything it closes over will leak." So if the callback is called, everything should be fine, but if not, there is still a memory leak. I found the programming style to be pretty nice. I mapped the JavaScript functions to Rust like this:

#[wasm_bindgen]
fn getSomething(details: &JsValue, callback: JsValue);

pub fn get_something(details: &Details, callback: impl Fn(Option<String>) + 'static){
    getSomething(&serde_wasm_bindgen::to_value(details).unwrap(), Closure::once_into_js(move |v: JsValue| 
        callback(serde_wasm_bindgen::from_value(v).unwrap())   
    ));
}

And then I'm able to use it from Rust in my app like so:

let callback = move |id| {
};
get_something(&details, callback);

I defined the callbacks as static impl functions and then move the values in.

Otocyst answered 21/1, 2020 at 12:5 Comment(0)
C
0

I use this when I'm sure it'll only be called once

let promise = Promise::resolve(&JsValue::NULL);
let ob = observer.clone();
type C = Closure<dyn FnMut(JsValue)>;
let drop_handler: Rc<RefCell<Option<C>>> = Rc::new(RefCell::new(None));
let copy = drop_handler.clone();
let closure = Closure::once(move |_: JsValue| {
    ob.call1(
        &Event {
            local: e.local,
            origin: e.origin.clone(),
        }
        .into(),
    );

    drop(copy);
});
let _ = promise.then(&closure);
drop_handler.borrow_mut().replace(closure);
Commissionaire answered 23/3, 2023 at 13:42 Comment(0)
M
0

Keep a reference to your closure somewhere. When you are done with it, unregister it (e.g. remove_event_listener_with_callback) and then drop it.

To make this more ergonomic*, I highly recommend reading this specific comment from the wasm-bindgen GitHub issue tracker.

* Get out your Rust bingo card! I said ergonomic.

Marjorie answered 21/5 at 20:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.