Why does i got lifetime error in rust closure

359 views Asked by At

I'm testing some rust wasm features, and have some problem with closures. I'm implemented this function, which setup callback on button click event.

pub fn setup_click(&mut self) {
    let mut clicks = 0;
    let ws_cloned = self.websocket.clone();
    let num_clicks_cloned = self.num_clicks.clone();
    let notifications = Rc::new(RefCell::new(Notificator::new(
        NotificationConfig::new_default(),
    )));
    let cb = move |_: Event| {
        clicks += 1;
        num_clicks_cloned
            .borrow_mut()
            .set_inner_html(clicks.to_string());
        let mut map: Map<String, Value> = serde_json::Map::new();
        map.insert("key".to_string(), Value::String(clicks.to_string()));
        if let Ok(ws) = ws_cloned.clone().try_borrow_mut() {
            ws.send_rpc(
                String::from("click"),
                Params::Map(map),
                Box::new(|payload: String| {
                    notifications.clone().borrow_mut().display(
                        payload,
                        "Click success".to_string(),
                        "success".to_string(),
                    )
                }),
            );
        }
    };
    self.click_button.add_event_listener("click", cb);
}

where third param of the ws.send rpc is

pub type RPCHandler = Box<dyn Fn(String) + 'static>;

and add_event_listener has this sugnature

pub fn add_event_listener<T>(&mut self, event_name: &str, handler: T)
where
    T: 'static + FnMut(web_sys::Event),
{
    let cb = Closure::wrap(Box::new(handler) as Box<dyn FnMut(_)>);
    if let Some(el) = self.el.take() {
        let el_et: EventTarget = el.into();
        el_et
            .add_event_listener_with_callback(event_name, cb.as_ref().unchecked_ref())
            .unwrap();
        cb.forget();
        if let Ok(el) = el_et.dyn_into::<web_sys::Element>() {
            self.el = Some(el);
        }
    }
}

When i try to compile the code i got life time error

  --> src/test_click_btn.rs:46:21
   |
35 |           let cb = move |_: Event| {
   |                    --------------- lifetime `'1` represents this closure's body
...
46 | /                     Box::new(|payload: String| {
47 | |                         notifications.clone().borrow_mut().display(
48 | |                             payload,
49 | |                             "Click success".to_string(),
50 | |                             "success".to_string(),
51 | |                         )
52 | |                     }),
   | |______________________^ cast requires that `'1` must outlive `'static`
   |
   = note: closure implements `FnMut`, so references to captured variables can't escape the closure```

I see that notifications not live long enough, but can't understand how to fix this error)
1

There are 1 answers

0
Kevin Reid On BEST ANSWER

There's no guarantee in this code that the closure passed to send_rpc will last no longer than the event callback closure. Therefore, it needs to be made a move closure too, so that it can live independently rather than borrowing from the event handler closure.

Conveniently, you already have notifications wrapped in Rc, which is just what you need, but you've performed the clone in the wrong place. This line

notifications.clone().borrow_mut().display(

performs a clone and dereferences it immediately, so it's redundant. Instead, you need to clone it before creating the closure so that the closure (now move) can own it:

    let notifications = notifications.clone();  // create a clone that will be moved into the closure
    ws.send_rpc(
        String::from("click"),
        Params::Map(map),
        Box::new(move |payload: String| {       // now a move closure
            notifications.borrow_mut().display( // no clone here
                ...