Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Borrow-check error with variable not living long enough in nested lambda

I am getting an error inside a nested lambda.

let rows = vec![
    vec![3, 6, 2, 8, 9, 0],
    vec![0, 0, 1, 4, 5, 1],
];

let pair_sums = rows.iter()
    .flat_map(|row| {
        (0 ..= row.len()).map(|i| row[i] + row[i + 1])
    })
    .collect::<Vec<_>>();

println!("{:?}", pair_sums);
error[E0597]: `row` does not live long enough
  --> src/main.rs:9:40
   |
9  |             (0..row.len() - 1).map(|i| row[i] + row[i + 1])
   |                                    --- ^^^ does not live long enough
   |                                    |
   |                                    capture occurs here
10 |         })
   |         - borrowed value only lives until here
11 |         .collect::<Vec<_>>();
   |                            - borrowed value needs to live until here

I can sort of see why this is happening, and I can fix it by threading the value of row through to the inner lambda:

let pair_sums = rows.iter()
    .flat_map(|row| { 
        (0 ..= row.len()).zip(iter::repeat(row))
            .map(|(i, row)| row[i] + row[i + 1])
    })
    .collect::<Vec<_>>();

This is horrible and can't be the best solution. How can I refer to variables in a parent scope without having to pass them along explicitly?

like image 625
Peter Hall Avatar asked Nov 09 '15 00:11

Peter Hall


1 Answers

The trick here is how closures capture their variables: they will take references to them if it is allowed by the contents of the closure, without looking at how they are used, to keep the inference local to the closure expression and predicable. In this case the row variable is only ever used by reference, so it is fine to be captured by reference; that is, the closure object passed to map contains a reference to row. This object hence cannot leave the scope that declares the row variable (i.e. flat_map's closure) because that reference would be left pointing to invalid memory. Returning .map(closure) will fall foul of this rule, as .map creates a lazy iterator that stores the closure and only calls it as elements are requested.

The fix here is to force the row variable to not be captured by reference, so that the closure can leave the scope. This can be done with the move keyword:

let pair_sums = rows.iter()
    .flat_map(|row| { 
        (0..row.len() - 1)
            .map(move |i| row[i] + row[i + 1])
    })
    .collect::<Vec<_>>();

In other words, the original code is equivalent to something like:

let pair_sums = rows.iter()
    .flat_map(|row: &Vec<i32>| { 
        let row_ref: &&Vec<i32> = &row;
        (0..row.len() - 1)
            .map(move |i| (*row_ref)[i] + (*row_ref)[i + 1])
    })
    .collect::<Vec<_>>();

(My Finding Closure in Rust post digs into closures in more detail, as does the Rust book.)

like image 167
huon Avatar answered Nov 10 '22 10:11

huon