Why may a closure outlive the current function by borrowing a u32?

Viewed 429

I'm getting

error[E0373]: closure may outlive the current function, but it borrows row_nr, which is owned by the current function

I would not expect this, since row_nr is a u32, so I'd expect it to be copied rather than moved:


fn get_neighbours(values: &Vec<Vec<u32>>, row: usize, col: usize) -> Vec<u32> {
    vec![
        values.get(row - 1).and_then(|cols| cols.get(col)),
        values.get(row).and_then(|cols| cols.get(col - 1)),
        values.get(row).and_then(|cols| cols.get(col + 1)),
        values.get(row + 1).and_then(|cols| cols.get(col)),
    ].into_iter().filter_map(|value_opt| value_opt.map(|value| *value)).collect()
}

fn get_points(values: Vec<Vec<u32>>) -> Vec<(u32, Vec<u32>)> {
    values
        .iter()
        .enumerate()
        .flat_map(|(row_nr, columns)| {
            columns.iter().enumerate().map(|(column_nr, height)| {
                let neighbours = get_neighbours(&values, row_nr, column_nr);
                (*height, neighbours)
            })
        }).collect()
}

Full error message:

  --> src/lib.rs:16:44
   |
16 |             columns.iter().enumerate().map(|(column_nr, height)| {
   |                                            ^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `row_nr`
17 |                 let neighbours = get_neighbours(&values, row_nr, column_nr);
   |                                                          ------ `row_nr` is borrowed here
   |
note: closure is returned here
  --> src/lib.rs:16:13
   |
16 | /             columns.iter().enumerate().map(|(column_nr, height)| {
17 | |                 let neighbours = get_neighbours(&values, row_nr, column_nr);
18 | |                 (*height, neighbours)
19 | |             })
   | |______________^
help: to force the closure to take ownership of `row_nr` (and any other referenced variables), use the `move` keyword
   |
16 |             columns.iter().enumerate().map(move |(column_nr, height)| {
   |                                            ++++

See also on the Rust playground.

Now, the error suggest using move, but that becomes problematic because I'd only like to pass a reference to the values Vec (which I don't think should outlive the closure, because get_neighbours takes u32s from that Vec and thus should make copies too, I think?).

I presume I'm misunderstanding lifetimes here. I'd appreciate any insights on what I'm misinterpreting. I looked at a couple of related questions, but they seem to either resolve it using a move (which, as mentioned above, I think I should (be able to) avoid?), or have multiple borrow checker issues, making it hard for me to understand which applies ones to me (e.g. this answer).

1 Answers

I would not expect this, since row_nr is a u32, so I'd expect it to be copied rather than moved

This expectation is correct, assuming it were moved in the first place. In this case it's not moved, it's borrowed, because by default closures borrow values from their environment. To request a move, which will for u32 indeed result in a copy, you need to use the move keyword explicitly.

As you discovered, when you just use move, you also move values, which doesn't compile and is anyway not what you want. The standard Rust idiom to move only one value into the closure is to use move and explicitly borrow the captured variables you don't want moved (in yout case values). For example, this compiles:

.flat_map(|(row_nr, columns)| {
    columns.iter().enumerate().map({
        let values = &values;
        move |(column_nr, height)| {
            let neighbours = get_neighbours(values, row_nr, column_nr);
            (*height, neighbours)
        }
    })
})

Playground

As for your titular question of why may a closure outlive the current function: Note that the "current function" in the error message refers to the outer closure, the one passed to flat_map(). Since the inner closure becomes part of the iterator returned by map(), and the outer closure immediately returns that iterator, it is trivially true that the inner closure does outlive the "current function" and that it cannot be allowed to borrow either row_nr or columns. (But borrowing values is perfectly fine because values outlives both closures.)

Related