I'm writing a function that should take a couple of vectors and produce their Cartesian product (all of their pair combinations) as a vector in row-major order. In other words, if I have
let x_coords = vec![1, 2, 3];
let y_coords = vec![4, 5, 6];
I want to produce
vec![ [1,4], [1,5], [1,6], [2,4], [2,5], [2,6], [3,4], [3,5], [3,6] ]
This seemed like a perfect job for .flat_map()
:
fn main() {
let x_coords = vec![1, 2, 3];
let y_coords = vec![4, 5, 6];
let coord_vec: Vec<[isize; 2]> =
x_coords.iter().map(|&x_coord| {
y_coords.iter().map(|&y_coord| {
[x_coord, y_coord]
})
}).flat_map(|s| s).collect();
// Expecting to get: vec![ [1,4], [1,5], [1,6], [2,4], [2,5], [2,6], [3,4], [3,5], [3,6] ]
println!("{:?}", &coord_vec);
}
But this doesn't work, because &x_coord
doesn't live long enough. According to the compiler, it ends up inside the y_coords
map and then never makes it back out.
I tried using .clone()
and move
in the closures, but got a strangely long and unclear lecture by the compiler in the form of multiple Note:
lines.
Am I just completely off base with flat_map
, or can this be saved?
You were really close! This works:
let coord_vec: Vec<_> = x_coords.iter()
.flat_map(|&x_coord| {
y_coords.iter().map(move |&y_coord| {
// ^^^^
[x_coord, y_coord]
})
})
.collect();
The only thing I added is the move
keyword in front of the inner closure. Why? Let's try to understand what the compiler is thinking below!
A few notes, though:
map
to flat_map
and removed the second flat_map
call... You made your life more complicated than it has to be ;-)coord_vec
, because it's not necessaryThe type x_coord
is i32
(or any other integer). Not a reference or anything but the value directly. This means that x_coord
is owned by the enclosing function, which happens to be a closure, specifically the "outer" closure, that you pass to flat_map
. Thus x_coord
lives just inside the closure, not longer. That's what the compiler is telling you. So far so good.
When you define the second closure (the "inner" one), you access the environment, specifically x_coord
. The important question now is: how does a closure access its environment? It can do so with an immutable reference, with a mutable reference and by value. The Rust compiler determines what kind of access to the environment is needed and chooses the "least intrusive" option that works. Let's look at your code: the compiler figures out that the closure only needs to borrow the environment immutably (because i32
is Copy
and thus the closure can transform its &i32
into a i32
easily).
But in this case, the compiler's reasoning is wrong! The closure borrowing its environment leads to a limited lifetime. And in this case we need the closure to live for longer than it can, hence the error.
By adding move
, we force the compiler to pass the environment to the closure by value (transferring ownership). This way the closure borrows nothing and can live forever (satisfies 'static
).