Callback with generic

Hi,

I'm trying to write a callback where I can register multiple callbacks and invoke them all.

The code looks like this:

pub struct Callback<T, F>
where
    F: FnMut(T),
{
    callbacks: Vec<Box<F>>,
    _phantom: PhantomData<T>,
}

impl<T, F> Callback<T, F>
where
    F: FnMut(T),
    T: Copy,
{
    pub fn new() -> Self {
        Callback {
            callbacks: Vec::new(),
            _phantom: PhantomData {},
        }
    }

    pub fn register(&mut self, callback: Box<F>)
    where
        F: FnMut(T),
    {
        self.callbacks.push(callback);
    }

    pub fn register_generic(&mut self, callback: F) {
        self.callbacks.push(Box::new(callback));
    }

    pub fn call(&mut self, val: T) {
        let repeat = "*".repeat(20);
        println!("{} Begin {}", repeat, repeat);
        for callback in self.callbacks.iter_mut() {
            // val is move to closure, we need T: Copy
            callback(val);
            // or
            // (&mut *callback)(val);
        }
        println!("{} End   {}", repeat, repeat);
    }
}

And playground:

here's the compiler error:

error[E0308]: mismatched types
  --> src/main.rs:53:25
   |
50 |     c.register(Box::new(|val| println!("Callback 1: {}", val)));
   |                         ------------------------------------- the expected closure
...
53 |     c.register(Box::new(|val| println!("XXX {}", val)));
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
   |
   = note: expected closure `[closure@src/main.rs:50:25: 50:62]`
              found closure `[closure@src/main.rs:53:25: 53:54]`
   = note: no two closures, even if identical, have the same type
   = help: consider boxing your closure and/or using it as a trait object

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `callback`

To learn more, run the command again with --verbose.

Why couldn't I invoke another register call with a new boxed closure as parameter?

Thanks

You need to erase the closure's original type (F) by turning it into a trait object (Box<dyn Fn(T)>). This lets you treat different closure types the same by adding a level of indirection (dynamic dispatch).

pub struct Callback<T> {
    callbacks: Vec<Box<dyn FnMut(T)>>,
}

impl<T> Callback<T> {
    pub fn register<F>(&mut self, callback: F)
    where
        F: FnMut(T) + 'static,
    {
        self.callbacks.push(Box::new(callback));
    }
}

I made register() generic over the particular closure being passed in, plus chose to accept the callback by value so the caller doesn't need to wrap it in a box first.

For simplicity, you probably don't want your Callback type to have lifetimes so I've also added the constraint that F: 'static. You can remove it if you want.

2 Likes

Thanks @Michael-F-Bryan for you help

It works. playground is here

1 Like

Also there's a no 'static lifetime version

Note that you don't need that Box when calling register, you're already boxing inside register's body, i.e. you can just write

c.register(|val| println!("Callback 1: {}", val));

@SkiFire13
That's correct, definitely no need to box again.
Thanks