I have just a quick question about the use of Rc inside Closures in wasm_bindgen.
The code below adds a click event to a paragraph. Randomly it will re-render the "app" and add a new paragraph to the DOM. The old <P> will be gone, but the state_clone (Rc) will still be around and Rc::strong_count(&state) will still increase the count.
How can I drop the Rc inside the Closure once the paragraph will be detached from the DOM?
Is it a big issue to have dangling Rc clones?
Will this magically be solved once I use --weak-refs?
Or, is there an other simpler way to achieve what I am trying to do?
Thanks a lot!
use std::{cell::RefCell, rc::Rc};
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::MouseEvent;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
fn render(state: Rc<RefCell<String>>) {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
body.set_inner_html("");
// print string count
log(format!("strong_count: {}", Rc::strong_count(&state)).as_str());
// crate paragraph
let p = document.create_element("p").unwrap();
p.set_inner_html("hello");
body.append_child(&p).unwrap();
// crate closure
let state_clone = Rc::clone(&state);
let on_click = Closure::<dyn Fn(MouseEvent)>::new(move |_e: MouseEvent| {
log(state_clone.borrow().as_str());
if js_sys::Math::random() > 0.5 {
log("re-render");
render(state_clone.clone());
// I would like to drop the Rc here
// std::mem::drop(state_clone);
}
});
p.add_event_listener_with_callback("click", on_click.as_ref().unchecked_ref())
.unwrap();
on_click.forget();
}
#[wasm_bindgen(start)]
pub fn main() {
let state = "hello".to_string();
let state = Rc::new(RefCell::new(state));
render(state);
}
The code is supposed to be structured such that the element which calls that callback ceases to exist right after the call which drops the Rc. This is not something we can enforce, however, so Rust has to err of the side of safety and not allow this. However, if this closure isn't used anywhere else, it's supposed to be garbage-collected on JS side anyway, right?
In my limited understanding of wasm_bindgen, I'm 99% sure this is not the case.
on_click.forget();
I'm 99% sure that is a (purposeful) memory leak and the JS side does not gc it. We leak memory on purpose since we pass a REFERENCE to the JS side in the previous line at:
If that is the case (I'm not sure it is); I think the solution here is to setup a Rc<RefCell<Option<Rc<Callback>>>> somewhere.
Then, in the callback, set this RefCell to None. In doing so, we cause the Rc<Callback> count to decrease by 1, and if that is the only ref to it, it gets GC-ed. However, care must then be taken to ensure that the JS side is no longer pointing to a non-existent ref in the Rust side.
Eventually yes, currently no. Once the weak references proposal is standardized and implemented wasm-bindgen will be able to generate JS that can run finalizers to clean up resources when the closure is garbage collected.
The thing you actually want to do is make sure the on_click closure gets dropped after you remove the child elements from the body. Instead of forgeting the closure you can store it similarly to how you're storing state. Then take the old closure and drop it, and all the captured variables should be dropped.