11

A is a structure which contains a vector of B. A implements the add_b method which adds a B instance to the list of B. B contains a closure property f.

If I add one B to the vector with add_b, it's OK. If I add two vectors with add_b, I got an error saying the two closures are different. Here is a minimal example:

// A struct...
struct A<F> {
    b_vec: Vec<B<F>>, // A vector of B
}

// ...and its implementation
impl<F> A<F>
where
    F: Fn(),
{
    fn new() -> A<F> {
        A { b_vec: Vec::new() }
    }

    fn add_b(&mut self, b: B<F>) {
        self.b_vec.push(b);
    }
}

// B struct...
struct B<F> {
    f: F,
}

// ...and its implementation
impl<F> B<F>
where
    F: Fn(),
{
    fn new(f: F) -> B<F> {
        B { f: f }
    }
}

// I add two B (with their closures arguments) in A
fn main() {
    let mut a = A::new();
    a.add_b(B::new(|| println!("test")));
    a.add_b(B::new(|| println!("test2")));
}

This code results in:

error[E0308]: mismatched types
  --> src/main.rs:39:20
   |
39 |     a.add_b(B::new(|| println!("test2")));
   |                    ^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
   |

How can I add multiple B with their different closures to A's b_vec?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
rap-2-h
  • 30,204
  • 37
  • 167
  • 263

2 Answers2

12

It's always worth taking a look at the full compiler output:

error[E0308]: mismatched types
  --> src/main.rs:39:20
   |
39 |     a.add_b(B::new(|| println!("test2")));
   |                    ^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
   |
   = note: expected type `[closure@src/main.rs:38:20: 38:39]`
              found type `[closure@src/main.rs:39:20: 39:40]`
   = note: no two closures, even if identical, have the same type
   = help: consider boxing your closure and/or using it as a trait object

Especially helpful:

  • no two closures, even if identical, have the same type

  • consider boxing your closure and/or using it as a trait object

We can simplify your example further by removing the type B altogether. Then the only task is to save a vector of closures. As the compiler tells us, no two closures have the same type, but Vec is a homogeneous data structure, meaning that every item in it has the same type.

We can work around that restriction by introducing one level of indirection. As the compiler suggests, this can either be done by trait objects or boxing (the latter kind of includes the first one). The corresponding types would look like this:

  • Vec<&dyn Fn()> (reference to trait objects)
  • Vec<Box<dyn Fn()>> (trait object in a box)

In your example you want to own all closures, thus the correct choice is to box all closures, as Box<T> is an owning wrapper while references only borrow stuff.

A fully working example:

struct A {
    b_vec: Vec<B>,
}

impl A {
    fn new() -> A {
        A { b_vec: Vec::new() }
    }

    fn add_b(&mut self, b: B) {
        self.b_vec.push(b);
    }
}

struct B {
    f: Box<dyn Fn()>,
}

impl B {
    fn new<F>(f: F) -> B
    where
        F: Fn() + 'static,
    {
        B { f: Box::new(f) }
    }
}

fn main() {
    let mut a = A::new();
    a.add_b(B::new(|| println!("test")));
    a.add_b(B::new(|| println!("test2")));
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • Thanks! FYI: I think I understood what you wrote, now fixing my code is not so easy (I'm still a beginner). I simplified my code by removing `B` (ok, check), then tried to add `Vec>` in struct declaration, then added `Box::new(...)` in `add_b` and still have the same error. Not sure to understand what I missed, I still have to dig. – rap-2-h Aug 22 '16 at 15:58
  • Rust playground here: http://play.integer32.com/?gist=74fa771c562d30c541db54b2e8cf8ec8 – rap-2-h Aug 22 '16 at 15:59
  • 1
    Might be worth nothing that if the closures actually do not capture any variable from the environment, then `fn()` is an appropriate type and does not require boxing. – Matthieu M. Aug 22 '16 at 16:05
  • 2
    @rap-2-h You literally have to write `Vec>` and not `F`. That's important. This way we do remove all type parameters. Here is a version that works: http://play.integer32.com/?gist=b387fb2439171547470e0047ec0f8f14 (this does the boxing outside of `add_b`; you can do it inside by adding a type parameter to the function, not `A`) – Lukas Kalbertodt Aug 22 '16 at 16:06
  • @LukasKalbertodt duplicate of http://stackoverflow.com/q/29371914/155423 ? – Shepmaster Aug 22 '16 at 16:35
  • 1
    @Shepmaster I've found that one, too, but *I* don't think it's a duplicate. OP in the linked question already uses `Box` and needs to specify the concrete trait-type. Here it's more or less the other way around... – Lukas Kalbertodt Aug 22 '16 at 16:54
6

In this specific example, you can avoid a trait object by using a function pointer instead:

struct B {
    f: fn(),
}

impl B {
    fn new(f: fn()) -> B {
        B { f: f }
    }
}

The rest of Lukas Kalbertodt's answer is unchanged:

struct A {
    b_vec: Vec<B>,
}

impl A {
    fn new() -> A {
        A { b_vec: Vec::new() }
    }

    fn add_b(&mut self, b: B) {
        self.b_vec.push(b);
    }
}

fn main() {
    let mut a = A::new();
    a.add_b(B::new(|| println!("test")));
    a.add_b(B::new(|| println!("test2")));
}

This is only valid because your closures do not capture any environment. The Rust compiler is thus able to "promote" them to full functions and then take references to the implicit function.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366