Reputation:
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 theClosure
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?
Upvotes: 11
Views: 2368
Reputation: 32705
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.
Upvotes: 0
Reputation: 387
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);
Upvotes: 0
Reputation: 6091
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.
Upvotes: 1