How to create a vector of boxed closures in Rust?


Keywords:rust 


Question: 

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)?


2 Answers: 

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<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<Trait>);
xs.push(Box::new(Type2) as Box<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<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<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.