Must `collect()` "early" to avoid "closure may outlive the current function" error in nested closure

#1

I am having trouble understanding why adding an early collect() call is necessary to prevent the error “closure may outlive the current function” error in nested closure" in my code.

Relevant snippet of the function (full version on the playground):

    input.iter().enumerate().flat_map(|(row_idx, row)| {
      let row_max = row.iter().max();
      row.iter().enumerate().filter_map(|(col_idx, &val)| {
          let col_min = col_mins[col_idx];
          // Side question: Why is Some(&val) required on the left and Some(val)
          // on the right in the conditional below?
          if Some(&val) == row_max && Some(val) == col_min {
            Some((row_idx, col_idx))
          } else {
            None
          }
        }).collect::<Vec<(usize, usize)>>() // <-- Why is this necessary?
        // Without the collect ^, the compiler complains about the filter_map closure:
        // "closure may outlive the current function, but it borrows `row_max`, which is owned by the current function"
        // If I add the `move` keyword to that closure, the compiler error changes to:
        // "cannot move out of captured variable in an `FnMut` closure"
    }).collect::<Vec<(usize, usize)>>()
#2

You don’t need to collect early,

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cf6ca8c49d76f1a9b225106c15b633c7

This misunderstanding is probably because you don’t know how closures work in Rust, I recommend reading my blog about closure desugaring here

Long story short, the closure is borrowing the data for too short a lifetime, but you can extend the lifetime by explicitly borrowing the relevant parts.

2 Likes
#3

@KrishnaSannasi answered your main question (although, strictly speaking, you don’t need the move in the outer closure), but to answer this one:

// Side question: Why is Some(&val) required on the left and Some(val)
// on the right in the conditional below?
          if Some(&val) == row_max && Some(val) == col_min {
            Some((row_idx, col_idx))
          } else {
            None
          }

The way the code is set up, row.iter().enumerate().filter_map(move |(col_idx, &val)| closure is receiving a &&u64 in the 2nd position of the tuple, but you’ve stripped one layer of reference away via the &val pattern, leaving you with &u64, and thus a Some(&u64) on the right side as well.

1 Like
#4

Thanks, this is very helpful and solved the problem. I’m reading the blog post now.

1 Like