0

Currently, I'm trying to implement a closure that captures one of the arguments to the enclosing function:

pub struct RegisteredHtmlEvent<'a> {
    event_target: EventTarget,
    type_: &'a str,
    closure: wasm_bindgen::closure::Closure<dyn std::ops::FnMut(Event)>
}

pub fn add_event_listener_state<'a, T>(&self, type_: &'a str, state: T, listener: Box<dyn Fn(&T, Event) -> ()>) -> Result<RegisteredHtmlEvent<'a>, JsValue> {
    let closure = Closure::wrap(Box::new(move |event| listener(&state, event)) as Box<dyn FnMut(Event)>);

    match self.event_target.add_event_listener_with_callback(type_, closure.as_ref().unchecked_ref()) {
        Err(e) => Err(e),
        _ => {
            Ok(RegisteredHtmlEvent {
                event_target: self.event_target.clone(),
                type_,
                closure: closure
            })
        }
    }
}

Naively, I assume that add_event_listener_state takes ownership of the state parameter. Then, the closure should take ownership of state.

But, compiling gives me the following error:

error[E0310]: the parameter type `T` may not live long enough
  --> src/web_sys_mixins.rs:48:34
   |
47 |     pub fn add_event_listener_state<'a, T>(&self, type_: &'a str, state: T, listener: Box<dyn Fn(&T, Event) -> ()>) -> Result<RegisteredHtmlEvent<'a>, JsValue> {
   |                                         - help: consider adding an explicit lifetime bound...: `T: 'static`
48 |            let closure = Closure::wrap(Box::new(move |event| listener(&state, event)) as Box<dyn FnMut(Event)>);
   |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `[closure@src/web_sys_mixins.rs:48:43: 48:79 listener:std::boxed::Box<dyn for<'r> std::ops::Fn(&'r T, web_sys::Event)>, state:T]` will meet its required lifetime bounds

Surprisingly, "T: Clone" doesn't help. Making the state parameter 'static causes problems with the caller because state is transient. (It's not static.)

How do I get the closure to take ownership of the state parameter without making it 'static?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Andrew Rondeau
  • 667
  • 7
  • 18
  • 1
    *Making the state parameter 'static causes problems with the caller because state is transient* - `T: 'static` doesn't mean `state` has to be static, it just means `T` can't contain any non-`'static` references. I can't tell whether that is the case here because I don't know the definitions of the types and functions involved. If that works the question would be a duplicate of [The compiler suggests I add a 'static lifetime because the parameter type may not live long enough, but I don't think that's what I want](/q/40053550/3650362) – trent Sep 27 '20 at 15:13

2 Answers2

1

Have the closure use state rather than &state. Writing &state borrows it and introduces a lifetime whereas state transfers ownership and has no lifetime you have to wrestle with.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • Unfortunately that doesn't work. This isn't an FnOnce. When I follow your suggestion, the error is rather cryptic. (And too big to post here.) What I'm trying to do is basically subscribe to an event, kind of like what we do in C#, or with GCD on Mac/iOS. In this case, I'm trying to encapsulate all the syntactic sugar that's needed to subscribe to DOM events in wasm; without reverting to static rust functions. (This is a learning project, not shipping code.) – Andrew Rondeau Sep 24 '20 at 01:28
  • How about `state.clone()`? – John Kugelman Sep 24 '20 at 02:49
  • state.clone() is the first thing I tried. (If it worked, I wouldn't have posted this SO question!) I suspect I'm hitting some kind of Rust corner case, or even a compiler bug. If I encapsulate state and listener into a boxed struct, (so that the closure's memory size is the same,) I start getting lifetime errors. I'd think it wouldn't matter, because the closure owns everything? – Andrew Rondeau Sep 27 '20 at 14:01
  • Found the answer to why clone() won't work. Looks like this is a limitation of wasm_bindgen. – Andrew Rondeau Sep 27 '20 at 14:59
0

Looks like this was a silly noob mistake on my part.

I need to declare this like: pub fn add_event_listener_state<'a, T: 'static>

Here's the updated method definition:

pub fn add_event_listener_state<'a, T: 'static>(&self, type_: &'a str, state: T, listener: Box<dyn Fn(&T, Event) -> ()>) -> Result<RegisteredHtmlEvent<'a>, JsValue> {

Thanks trentcl!

Andrew Rondeau
  • 667
  • 7
  • 18
  • I'm not sure this answers it, though. Values that are moved into the closure should have `'static` lifetime. IOW, if you don't have any references you should be fine. Why didn't `clone()` work? – John Kugelman Sep 27 '20 at 15:01
  • The fact that clone() didn't work surprised me. Honestly, I'm too much of a Rust and wasm_bindgen novice to explain why in detail. What does happen is that, if I were to use "state: &'static T", the caller has problems because its state value is transient. (Not static) – Andrew Rondeau Sep 27 '20 at 15:14
  • 2
    The error message doesn't tell you to try `&'static T`, it tells you to try `T: 'static`. The Q&A I linked in the question comments explains why they're different (and why `T: 'static` may work when `&'static T` does not apply). – trent Sep 27 '20 at 17:26
  • @trentcl: I wish you made an answer in this thread, I'd have given you the credit! – Andrew Rondeau Sep 28 '20 at 18:40
  • Glad I could help! I'm content with this question being marked as a duplicate of the other one, since that will direct future question askers to relevant answers. – trent Sep 28 '20 at 18:56