rustclosureslifetimeborrow-checker

Iterator over Vec<Vec<i32>>: closure may outlive the current function, but it borrows


I'm writing a function to process Vec<Vec<i32>>:

fn process_grid(grid: Vec<Vec<i32>>) -> Vec<i32> {
    grid.iter()
        .enumerate()
        .flat_map(|(i, row)| {
            row.iter()
                .enumerate()
                 // closure may outlive the current function, 
                 // but it borrows `i`, which is owned by the current function
                .flat_map(|(j, val)| process(i, j, *val))
        })
        .collect()
}

fn process(i: usize, j: usize, val: i32) -> Option<i32> {
    todo!()
}

The full compile error looks as follow:

error[E0373]: closure may outlive the current function, but it borrows `i`, which is owned by the current function
   --> src/main.rs:291:27
    |
291 |                 .flat_map(|(j, val)| process(i, j, *val))
    |                           ^^^^^^^^^^         - `i` is borrowed here
    |                           |
    |                           may outlive borrowed value `i`
    |
note: closure is returned here
   --> src/main.rs:289:13
    |
289 | /             row.iter()
290 | |                 .enumerate()
291 | |                 .flat_map(|(j, val)| process(i, j, *val))
    | |_________________________________________________________^
help: to force the closure to take ownership of `i` (and any other referenced variables), use the `move` keyword
    |
291 |                 .flat_map(move |(j, val)| process(i, j, *val))
    |                           ++++

Adding the move keyword indeed make things work, but it didn't make it clear on why it does not compile without move.


How is it even possible the closure |(j, val)| process(i, j, *val) may outlive its enclosing closure:

|(i, row)| {
    row.iter()
        .enumerate()
        // closure may outlive the current function, 
        // but it borrows `i`, which is owned by the current function
        .flat_map(|(j, val)| process(i, j, *val))
}

Maybe placing explicit lifetime would help here?


Solution

  • The compiler has chosen that the closure in question only references i, and since that is captured by reference and iterators are lazy, this will effectively try to return a reference to i from the outer flat_map closure, which isn't allowed since i only exists for that call.

    In this case it is sufficient to tell the compiler to move captures into the closure by adding the move keyword like so:

    fn process_grid(grid: Vec<Vec<i32>>) -> Vec<i32> {
        grid.iter()
            .enumerate()
            .flat_map(|(i, row)| {
                row.iter()
                    .enumerate()
                    .flat_map(move |(j, val)| process(i, j, *val))
                    //        ^^^^
            })
            .collect()
    }