Where to implement trait bound on enum variant

I'm trying to combine two functions with the following signatures.

fn add_event_listener<T>( &mut self, name: &str, callback: T, group: &str, id: &str)
where
    T: FnMut(Event) + 'static,
{
    let element = Template::get_element_by_id(id);
    let target: EventTarget = element.into();
    let cb = Closure::<dyn FnMut(Event)>::new(callback);
    let name_string = String::from(name);

    // snip
}
fn add_progress_listener<T>( &mut self, name: &str, callback: T, group: &str, reader: FileReader
) where
    T: FnMut(ProgressEvent) + 'static,
{
    let target: EventTarget = reader.into();
    let cb = Closure::<dyn FnMut(ProgressEvent)>::new(callback);
    let name_string = String::from(name);

    // snip
}

I put the variables in a struct with enums...

enum AddListenerTarget<'a> {
    Str(&'a str),
    FileReader(FileReader),
}
enum AddListenerGroup {
    GroupA,
    GroupB,
}
enum EventType {
    Evt(Event),
    PrgEvt(ProgressEvent),
}
struct AddListener<'a> {
    name: String,
    callback: EventType,
    group: AddListenerGroup,
    target: AddListenerTarget<'a>,
}

...but now I don't know how to implement the trait bounds I lost.

fn add_listener(&mut self, l: AddListener) { // <-- no "where" clause
    let name_string = String::from(l.name);

    use EventType::*;
    match l.callback {
        Evt(callback) => {
            if let AddListenerTarget::Str(id) = l.target {
                let element = Template::get_element_by_id(id);
                let target: EventTarget = element.into();
                let cb = Closure::<dyn FnMut(Event)>::new(callback); // <-- needs trait bound

                // snip
            }
        }
        PrgEvt(callback) => {
            if let AddListenerTarget::FileReader(reader) = l.target {
                let target: EventTarget = reader.into();
                let cb = Closure::<dyn FnMut(ProgressEvent)>::new(callback); // <-- needs trait bound

                // snip
            }
        }
    }

Error (one for each closure)

error[E0277]: the trait bound `Event: IntoWasmClosure<dyn FnMut(Event)>` is not satisfied
   --> src/view/mod.rs:177:63
    |
177 |                     let cb = Closure::<dyn FnMut(Event)>::new(callback);
    |                              -------------------------------- ^^^^^^^^ the trait `FnMut(Event)` is not implemented for `Event`
    |                              |
    |                              required by a bound introduced by this call
    |
    = note: required for `Event` to implement `IntoWasmClosure<dyn FnMut(Event)>`
note: required by a bound in `Closure::<T>::new`
   --> /rust/cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wasm-bindgen-0.2.100/src/closure.rs:274:12
    |
272 |     pub fn new<F>(t: F) -> Closure<T>
    |            --- required by a bound in this associated function
273 |     where
274 |         F: IntoWasmClosure<T> + 'static,
    |            ^^^^^^^^^^^^^^^^^^ required by this bound in `Closure::<T>::new`

I tried putting the trait bounds on the enum but it did not go well. Any suggestions? Thanks in advance.

Before, you were passing a generic T: FnMut(X) + 'static to Closure::new, where X was Event or ProgressEvent. Now you're passing Event or ProgressEvent to Closure::new.

You probably don't really want to mirror the old way directly. If you did so, it would look something like...

enum EventType<EvtCb, PrgEvtCb> {
    Evt(EvtCb),
    PrgEvt(PrgEvtCb)
}

struct AddListener<'a, EvtCb, PrgEvtCb> {
    name: String,
    callback: EventType<EctCb, PrgEvtCb>,
    group: AddListenerGroup,
    target: AddListenerTarget<'a>,
}

fn add_listener<EvtCb, PrgEvtCb>(&mut self, l: AddListener<EvtCb, PrgEvtCb>)
where
   EvtCb: FnMut(Event) + 'static,
   PrgEvtCb: FnMut(ProgressEvent) + 'static,
{

...and the reason you probably don't want to do this is that when the consumer of these APIs has a .callback = EventType::Event(|_| { ... }), they would still have to specify the type of the PrgEvtCb somehow. They could use fn(Event), but it won't be very ergonomic.

You could maybe work around that somewhat I suppose...

impl<EvtCb> EventType<EvtCb, fn(ProgressEvent)>
where
    EvtCb: FnMut(Event) + 'static,
{
    fn new_event(cb: EvtCb) -> Self {
        Self::Evt(cb)
    }
}

impl<PrgEvtCb> EventType<fn(Event), PrgEvtCb>
where
    PrgEvtCb: FnMut(ProgressEvent) + 'static,
{
    fn new_progress_event(cb: PrgEvtCb) -> Self {
        Self::PrgEvt(cb)
    }
}

// ...
whatever.callback = EventType::new_event(|_| { ... })

But I'm getting the feel that the abstraction is "in the wrong place".

Here's a demonstration if you want to pursue it anyway.

Thank you for the reply: I was also scrutinizing Rust Docs: Bounds and other pages for clues. I'm going to see how far I can get with the samples you provided.

Thanks again! I ended up using much of what you did in the demo.

E.g.:

enum ELCallback<T, U> {
    Evt(T),
    PrEvt(U),
}

impl<T> ELCallback<T, fn(ProgressEvent)>
where
    T: FnMut(Event) + 'static,
{
    fn event(cb: T) -> Self {
        Self::Evt(cb)
    }
}

impl<T> ELCallback<fn(Event), T>
where
    T: FnMut(ProgressEvent) + 'static,
{
    fn progress_event(cb: T) -> Self {
        Self::PrEvt(cb)
    }
}

This block is useful to me because until now, I had only seen impl in the form of

  • impl X
  • impl X for Y

Two observations:

  1. This solution works if the callback remains a standalone parameter to the add_listener function (you can specify a where clause on it). Making it a field in a struct is a no-go.
  2. The where clause is needed both in the function -and- in the impl for each enum variant.