11

Previously a question was asked about creating an array of functions where the functions returned integers from a range. The final solution was to do a map/collect into a Vec<_>.

I have a similar yet different situation where I have closures with the same signature but different implementations. I tried this:

let xs: Vec<_> = vec![
    move |(x, y)| (y, x),
    move |(x, y)| (1 - y, 1 - x),
];

The error I get back:

error[E0308]: mismatched types
 --> src/main.rs:4:9
  |
4 |         move |(x, y)| (1 - y, 1 - x),
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
  |
  = note: expected type `[closure@src/main.rs:3:9: 3:29]`
             found type `[closure@src/main.rs:4:9: 4:37]`
  = note: no two closures, even if identical, have the same type
  = help: consider boxing your closure and/or using it as a trait object

I tried boxing:

let xs: Vec<_> = vec![
    Box::new(move |x: u8, y: u8| (y, x)),
    Box::new(move |x: u8, y: u8| (1 - y, 1 - x)),
];

I get back the same error:

error[E0308]: mismatched types
 --> src/main.rs:4:18
  |
4 |         Box::new(move |x: u8, y: u8| (1 - y, 1 - x)),
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found a different closure
  |
  = note: expected type `[closure@src/main.rs:3:18: 3:44]`
             found type `[closure@src/main.rs:4:18: 4:52]`
  = note: no two closures, even if identical, have the same type
  = help: consider boxing your closure and/or using it as a trait object

What is the right way to box closures so that they can be put into a vector (or an array)?

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

2 Answers2

26

The problem is that type inference has kicked in before you wanted it to. Conceptually, it's like this:

let mut xs: Vec<_> = Vec::new();
xs.push(Type1);
xs.push(Type2);

When the first value is seen, the type of the Vec's elements is inferred to be of that type. The second element then causes a mismatch.

Even when you Box the values, you have the same problem:

let mut xs: Vec<_> = Vec::new();
xs.push(Box::new(Type1));
xs.push(Box::new(Type2));

Looking at it another way, you never actually created a trait object. You have a Box<ConcreteType>, not a Box<dyn Trait>.

The solution is to cast the boxed concrete types to the boxed trait object:

let mut xs: Vec<_> = Vec::new();
xs.push(Box::new(Type1) as Box<dyn Trait>);
xs.push(Box::new(Type2) as Box<dyn Trait>);

The second push can have the type coerced automatically, so you can choose to leave the as bit off of that line.

Rolling this back up to the original problem:

let xs: Vec<_> = vec![
    Box::new(move |(x, y)| (y, x)) as Box<dyn Fn((i32, i32)) -> (i32, i32)>,
    Box::new(move |(x, y)| (1 - y, 1 - x)),
];

Or you can avoid the inference at all by specifying the type on the variable, my preferred style for this:

let xs: Vec<Box<dyn Fn((i32, i32)) -> (i32, i32)>> = vec![
    Box::new(move |(x, y)| (y, x)),
    Box::new(move |(x, y)| (1 - y, 1 - x)),
];
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
5

You should read the suggestion in the error message as "consider boxing your closure and using it as a trait object, or using it just as a trait object".

Using trait object references without boxing them will not work here because nothing owns the closures. The references in the vector would outlive the closures:

// This fails
let xs: Vec<&Fn((i32, i32)) -> (i32, i32)> = vec![ 
    &move |(x, y)| (y, x), 
    &move |(x, y)| (1 - y, 1 - x),
];

The vector needs to take ownership of the closures, which is where boxing the trait objects comes into play:

let xs: Vec<Box<Fn((i32, i32)) -> (i32, i32)>> = vec![ 
    Box::new(move |(x, y)| (y, x)), 
    Box::new(move |(x, y)| (1 - y, 1 - x)),
];

This explicitly tells the compiler that the vector can contain boxes of any closure with the same interface.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
MB-F
  • 22,770
  • 4
  • 61
  • 116
  • Although this answer shows the correct end code, the OP *already* tried boxing the closures, as shown in the question. Lifetimes also do not play a part in the problem. – Shepmaster Feb 27 '18 at 15:50
  • @Shepmaster What lifetimes are you referring to? – MB-F Feb 27 '18 at 15:55
  • That was a poor choice of words on my part. I was referring to your statement "the closures are temporary and the vector would outlive them". They are not *temporary*, the `Vec` would own them, it's just that you cannot own the concrete value and have the `Vec`'s type be a reference. – Shepmaster Feb 27 '18 at 16:02
  • @Shepmaster The error message seems to suggest the problem could be solved without boxing. My intent was to point out why this fails, but forgot to insert the `&`s. In that case there is actually a lifetime issue. – MB-F Feb 27 '18 at 16:08