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?
Solution
You were really close! This works:
The only thing I added is the
movekeyword in front of the inner closure. Why? Let's try to understand what the compiler is thinking below!A few notes, though:
maptoflat_mapand removed the secondflat_mapcall... You made your life more complicated than it has to be ;-)coord_vec, because it's not necessaryExplanation
The type
x_coordisi32(or any other integer). Not a reference or anything but the value directly. This means thatx_coordis owned by the enclosing function, which happens to be a closure, specifically the "outer" closure, that you pass toflat_map. Thusx_coordlives 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 (becausei32isCopyand thus the closure can transform its&i32into ai32easily).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).