rustwebassemblywasm-bindgenrust-wasm

How can I create a wasm_bindgen Closure which captures a Rc<RefCell<_>> without moving out of it?


I have state that needs to be mutated multiple times by JavaScript callbacks (it's a game, so input from the user needs to modify the game state). I can't seem to figure out how to do it though. This is the simplest example I can think of that demonstrates the problem I'm having:

#[wasm_bindgen(start)]
pub fn run() {
    let data = Rc::new(RefCell::new(vec![0]));
    let closure = Closure::wrap(Box::new(&mut || {
        data.borrow_mut().push(0);
        console_log!("{data:?}");
    }) as Box<dyn FnMut()>);

    closure.forget();
}

I thought that using an Rc would make data live as long as it's used, but I get this error:

error[E0716]: temporary value dropped while borrowed
  --> src/lib.rs:19:47
   |
19 |        let closure = Closure::wrap(Box::new(&mut || {
   |   _________________________________-_____________^
   |  |_________________________________|
   | ||
20 | ||         data.borrow_mut().push(0);
21 | ||         console_log!("{data:?}");
22 | ||     }) as Box<dyn FnMut()>);
   | ||     ^-                     - temporary value is freed at the end of this statement
   | ||_____||
   |  |_____|cast requires that borrow lasts for `'static`
   |        creates a temporary which is freed while still in use

error[E0597]: `data` does not live long enough
  --> src/lib.rs:20:9
   |
19 |       let closure = Closure::wrap(Box::new(&mut || {
   |                                   -             -- value captured here
   |  _________________________________|
   | |
20 | |         data.borrow_mut().push(0);
   | |         ^^^^ borrowed value does not live long enough
21 | |         console_log!("{data:?}");
22 | |     }) as Box<dyn FnMut()>);
   | |______- cast requires that `data` is borrowed for `'static`
...
25 |   }
   |   - `data` dropped here while still borrowed

How can I fix this problem?


Solution

  • Rc can make it live enough, but currently you're borrowing the Rc itself which is destroyed at the end of run(). You need to clone it, and move the clone into the closure:

    #[wasm_bindgen(start)]
    pub fn run() {
        let data = Rc::new(RefCell::new(vec![0]));
        let closure = Closure::wrap(Box::new({
            let data = Rc::clone(&data);
            &mut move || {
                data.borrow_mut().push(0);
                console_log!("{data:?}");
            }
        }) as Box<dyn FnMut()>);
    
        closure.forget();
    }
    

    You also need to not borrow the closure itself, which is the reason for the "temporary value dropped while borrowed" error. Replace the &mut move || { with just move || {.