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)?
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)),
];
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.