Fighting the borrow checker: level 2

I'm trying to extend the example dom in wasm-bindgen to encapsulate some of the logic, and handle callbacks, but I'm getting a lifetime error:

The code
//! A simple example of wrapping the raw DOM calls to make it more rust-y.

extern crate wasm_bindgen;
extern crate web_sys;

use wasm_bindgen::prelude::*;

// Called by our JS entry point to run the example
#[wasm_bindgen]
pub fn run() -> Result<(), JsValue> {
    let el = Element::new("p")?;
    el.set_inner_html("Hello from Rust!");
    el.append_to_body()?;
    Ok(())
}

#[derive(Debug, Copy, Clone)]
pub struct Element(&'static web_sys::Element);

impl Element {
    pub fn new(tag_name: &str) -> Result<Self, JsValue> {
        // these methods should never fail.
        let document = web_sys::window().unwrap().document().unwrap();
        let el = document.create_element(tag_name)?;
        Ok(Element(&el))
    }

    pub fn set_inner_html(&self, content: &str) {
        self.0.set_inner_html(content)
    }

    pub fn append_to_body(&self) -> Result<(), JsValue> {
        let body = web_sys::window().unwrap().document().unwrap().body().unwrap();
        AsRef::<web_sys::Node>::as_ref(&body).append_child(self.0.as_ref())?;
        Ok(())
    }

    pub fn set_on_click<F>(&self, callback: F) where F: Fn(Element) {
        let closure = Closure::wrap(Box::new(move || {
            callback(self.clone())
        }) as Box<Fn()>);
    }
}

I'm confused as to why I'm getting a lifetime error, because my Element is Copy, and has no lifetime.

Can anyone help! :slight_smile:

An alternative formulation without any 'static

The code
//! A simple example of wrapping the raw DOM calls to make it more rust-y.

extern crate wasm_bindgen;
extern crate web_sys;

use wasm_bindgen::prelude::*;

// Called by our JS entry point to run the example
#[wasm_bindgen]
pub fn run() -> Result<(), JsValue> {
    let el = Element::new("p")?;
    el.set_inner_html("Hello from Rust!");
    el.append_to_body()?;
    Ok(())
}

#[derive(Debug, Clone)]
pub struct Element(web_sys::Element);

impl Element {
    pub fn new(tag_name: &str) -> Result<Self, JsValue> {
        // these methods should never fail.
        let document = web_sys::window().unwrap().document().unwrap();
        let el = document.create_element(tag_name)?;
        Ok(Element(el))
    }

    pub fn set_inner_html(&self, content: &str) {
        self.0.set_inner_html(content)
    }

    pub fn append_to_body(&self) -> Result<(), JsValue> {
        let body = web_sys::window().unwrap().document().unwrap().body().unwrap();
        AsRef::<web_sys::Node>::as_ref(&body).append_child(self.0.as_ref())?;
        Ok(())
    }

    pub fn set_on_click<F>(&self, callback: F) where F: Fn(Element) {
        let closure = Closure::wrap(Box::new(move || {
            callback(self.clone())
        }) as Box<Fn()>);
    }
}

Since this doesn't run on the playground, maybe you could post the error message?

Sure:

Error message
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> examples/dom-wrapper/src/lib.rs:39:46
   |
39 |           let closure = Closure::wrap(Box::new(move || {
   |  ______________________________________________^
40 | |             callback(self.clone())
41 | |         }) as Box<Fn()>);
   | |_________^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 38:5...
  --> examples/dom-wrapper/src/lib.rs:38:5
   |
38 | /     pub fn set_on_click<F>(&self, callback: F) where F: Fn(Element) {
39 | |         let closure = Closure::wrap(Box::new(move || {
40 | |             callback(self.clone())
41 | |         }) as Box<Fn()>);
42 | |     }
   | |_____^
   = note: ...so that the types are compatible:
           expected &Element
              found &Element
   = note: but, the lifetime must be valid for the static lifetime...
   = note: ...so that the types are compatible:
           expected wasm_bindgen::closure::WasmClosure
              found wasm_bindgen::closure::WasmClosure

You're capturing a borrow of self in the closure, so just clone outside:

let closure = Closure::wrap(Box::new({
            let clone = self.clone();
            move || {
                callback(clone)
            }
        }) as Box<Fn()>);

Thanks! This solved the issue. I now have a different error message:

New error
error[E0277]: expected a `std::ops::Fn<()>` closure, found `[closure@examples/dom-wrapper/src/lib.rs:41:46: 43:10 callback:std::boxed::Box<dyn std::ops::Fn(Element)>, self2:Element]`
  --> examples/dom-wrapper/src/lib.rs:41:37
   |
41 |           let closure = Closure::wrap(Box::new(move || {
   |  _____________________________________^
42 | |             (*callback)(self2)
43 | |         }) as Box<Fn()>);
   | |__________^ expected an `Fn<()>` closure, found `[closure@examples/dom-wrapper/src/lib.rs:41:46: 43:10 callback:std::boxed::Box<dyn std::ops::Fn(Element)>, self2:Element]`
   |
   = help: the trait `std::ops::Fn<()>` is not implemented for `[closure@examples/dom-wrapper/src/lib.rs:41:46: 43:10 callback:std::boxed::Box<dyn std::ops::Fn(Element)>, self2:Element]`
   = note: wrap the `[closure@examples/dom-wrapper/src/lib.rs:41:46: 43:10 callback:std::boxed::Box<dyn std::ops::Fn(Element)>, self2:Element]` in a closure with no arguments: `|| { /* code */ }
   = note: required for the cast to the object type `dyn std::ops::Fn()
Updated code
//! A simple example of wrapping the raw DOM calls to make it more rust-y.

extern crate wasm_bindgen;
extern crate web_sys;

use wasm_bindgen::prelude::*;

// Called by our JS entry point to run the example
#[wasm_bindgen]
pub fn run() -> Result<(), JsValue> {
    let el = Element::new("p")?;
    el.set_inner_html("Hello from Rust!");
    el.append_to_body()?;
    Ok(())
}

#[derive(Debug, Clone)]
pub struct Element(web_sys::Element);

impl Element {
    pub fn new(tag_name: &str) -> Result<Self, JsValue> {
        // these methods should never fail.
        let document = web_sys::window().unwrap().document().unwrap();
        let el = document.create_element(tag_name)?;
        Ok(Element(el))
    }

    pub fn set_inner_html(&self, content: &str) {
        self.0.set_inner_html(content)
    }

    pub fn append_to_body(&self) -> Result<(), JsValue> {
        let body = web_sys::window().unwrap().document().unwrap().body().unwrap();
        AsRef::<web_sys::Node>::as_ref(&body).append_child(self.0.as_ref())?;
        Ok(())
    }

    pub fn set_on_click<F>(&self, callback: F) where F: Fn(Element) {
        let self2 = self.clone();
        let closure = Closure::wrap(Box::new(move || {
            callback(self2)
        }) as Box<Fn()>);
    }
}

Now I'm confused because the advice it give me (about wrapping the closure) I've already done! :stuck_out_tongue:

I think this is because your closure can't implement Fn() because F (i.e. callback) consumes the Element you give it, and an Fn must be callable many times. So, you can either make F: Fn(&Element) and pass it a &self2 or make the closure do callback(self2.clone()).

You're right. I also needed F: 'static. Thanks for the help :slight_smile: