How to get out of this "move" hell?

Whats the right "move" to make this work?

fn three_sum(nums: Vec<i32>) -> Vec<Vec<i32>> {
      let itera = nums.iter().copied().enumerate();
      let iterb = itera.clone();
      let iterc = iterb.clone();

      itera.flat_map(|(i, u)| {
          iterb.filter(move |(j, ..)| j != &i)
              .flat_map(move |(j, v)| {
                  iterc.filter(move |&(k, w)| j != k && k != i && u + v + w == 0)
                      .map(|(.., w)| vec![u, v, w])
              })
      }).collect()
  }

First, it looks like you want a new version of the iterator at each level of the nesting, and not just a single clone for the second and third level at the top, right? So the first step would be creating a new iterator inside each flat_map. iter_a is consumed by then, so we can't clone it; let's just make the same iterator anew.

    itera
        .flat_map(|(i, u)| {
            let iterb = nums.iter().copied().enumerate();
            iterb
                .filter(move |(j, ..)| j != &i)
                .flat_map(move |(j, v)| {
                    let iterc = nums.iter().copied().enumerate();
                    iterc
                        .filter(move |&(k, w)| j != k && k != i && u + v + w == 0)
                        .map(|(.., w)| vec![u, v, w])
                })
        })
        .collect()

But Rust still doesn't like this because you're move-ing nums everywhere now. You can dodge this one by only moving a reference to nums instead, because shared references implement Copy.

fn three_sum(nums: Vec<i32>) -> Vec<Vec<i32>> {
    let nums = &nums;
    // ...

Then I still get a

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

But everything we're working with at this point should be Copy (shared references or i32), so I just slap one more move on the guilty closure (the last closure in the function).

Here's the end result.

Incidentally, at this point it's clear that since you're only iterating things out and copying, you never really needed to consume the Vec (nums), and you could change the signature to

fn three_sum(nums: &[i32]) -> Vec<Vec<i32>> {
// And no more need for `let nums = &nums` as it's already a shared ref.

Basically this is a messy exercise of working around move closures being everything-or-nothing. The workaround was making the "everything" case work by making everything Copy, since you didn't need anything more than a shared reference to nums anyway.

Edit: Alternatively, use for loops to avoid so many nested closures.

4 Likes

Thanks for the explanation

Here’s another solution

fn three_sum(nums: Vec<i32>) -> Vec<Vec<i32>> {
    let iter = &nums.iter().copied().enumerate();

    iter.clone()
        .flat_map(|(i, u)| {
            iter.clone()
                .filter(move |(j, ..)| j != &i)
                .flat_map(move |(j, v)| {
                    iter.clone()
                        .filter(move |&(k, w)| j != k && k != i && u + v + w == 0)
                        .map(move |(.., w)| vec![u, v, w])
                })
        })
        .collect()
}
2 Likes