Search code examples
rustclosuresownership

How do I write a lazily evaluated double for loop in a functional style?


How do I write a lazily evaluated double for loop in a functional style in Rust?

The borrowed value is of type usize, which should be trivially copyable.

fn main() {
    let numbers: Vec<i32> = (1..100).collect();
    let len = numbers.len();

    let _sums_of_pairs: Vec<_> = (0..len)
        .map(|j| ((j + 1)..len).map(|k| numbers[j] + numbers[k]))
        .flatten()
        .collect();
}
error[E0373]: closure may outlive the current function, but it borrows `j`, which is owned by the current function
 --> src/bin/example.rs:6:37
  |
6 |         .map(|j| ((j + 1)..len).map(|k| numbers[j] + numbers[k]))
  |                                     ^^^         - `j` is borrowed here
  |                                     |
  |                                     may outlive borrowed value `j`
  |
note: closure is returned here
 --> src/bin/example.rs:6:18
  |
6 |         .map(|j| ((j + 1)..len).map(|k| numbers[j] + numbers[k]))
  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `j` (and any other referenced variables), use the `move` keyword
  |
6 |         .map(|j| ((j + 1)..len).map(move |k| numbers[j] + numbers[k]))
  |                                     ^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0373`.

Further Notes

  • I am aware that Itertools::combinations(2) does the job. However, I don't want to use it because (1) I want to know how to do it myself and (2) it might be the reason my code is slow, and I want to eliminate that source. (Update: Itertools::tuple_combinations<(_, _)>() is much, much faster and lets one code this in a functional style.)
  • I also tried collecting it into a container first. (0..len).collect::<Vec<_>>().iter().cloned().map(...)
  • I tried the suggested move but then numbers is also moved and hence not available in the next loop.
  • There is no threading or async happening anywhere in this code example.
  • Shepmaster says in this answer that I cannot make lifetime annotations on closures.
  • The reason I don't write two raw loops with early return is, that if I want to say, run .any() to find if a specific value is present, I'd have to move the two loops into a separate function as I cannot put return true; inside the loop unless it's in a separate function.

Solution

  • To work around the issue, you can borrow &numbers up front and just shadow numbers. Then after that you can add move to the second closure.

    fn main() {
        let numbers: Vec<i32> = (1..100).collect();
        let len = numbers.len();
    
        let numbers = &numbers;
    
        let _sums_of_pairs: Vec<_> = (0..len)
            .map(|j| ((j + 1)..len).map(move |k| numbers[j] + numbers[k]))
            .flatten()
            .collect();
    }