14

I am trying to make a 2-dimensional matrix from a functor that creates each element, and store it as a flat Vec (each row concatenated).

I used nested map (actually a flat_map and a nested map) to create each row and concatenate it. Here is what I tried:

fn make<T, F>(n: usize, m: usize, f: F) -> Vec<T>
where
    F: Fn(usize, usize) -> T,
{
    (0..m).flat_map(|y| (0..n).map(|x| f(x, y))).collect()
}

fn main() {
    let v = make(5, 5, |x, y| x + y);

    println!("{:?}", v);
}

Unfortunately, I get an error during compilation:

error[E0597]: `y` does not live long enough
 --> src/main.rs:5:45
  |
5 |     (0..m).flat_map(|y| (0..n).map(|x| f(x, y))).collect()
  |                                    ---      ^ -          - borrowed value needs to live until here
  |                                    |        | |
  |                                    |        | borrowed value only lives until here
  |                                    |        borrowed value does not live long enough
  |                                    capture occurs here

How does one use closures in nested maps? I worked around this issue by using a single map on 0..n*m, but I'm still interested in the answer.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Gyscos
  • 1,772
  • 17
  • 22
  • Perhaps a duplicate of http://stackoverflow.com/q/28521637/155423 ? – Shepmaster Jun 04 '15 at 23:19
  • I hoped since y:usize was Copy-able, it could be taken by value by the inner closure, thus avoiding the need for any borrowing... – Gyscos Jun 04 '15 at 23:44
  • I agree, and I'm a bit surprised that there's an error here at all — I have a feeling it has to do with that `FnMut`. There may also be a real bug, as sprinkling `.clone()` on all the variables doesn't fix the problem as I thought it would either. – Shepmaster Jun 04 '15 at 23:47

2 Answers2

11

In your case the inner closure |x| f(x,y) is a borrowing closure, which takes its environment (y and f) by reference.

The way .flat_map(..) works, it forbids you to keep a reference to y, which is not from the outer scope. Thus we need to have your closure take its environment by value, which is not a problem for y being a usize which is Copy:

(0..m).flat_map(|y| (0..n).map(move |x| f(x, y))).collect()

However, now another problem arises:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
 --> src/main.rs:5:36
  |
1 | fn make<T, F>(n: usize, m: usize, f: F) -> Vec<T>
  |                                   - captured outer variable
...
5 |     (0..m).flat_map(|y| (0..n).map(move |x| f(x,y))).collect()
  |                                    ^^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure

Here, we are trying to move f as well into the closure, which is definitely not possible (unless m is 1, but the compiler cannot know that).

Since f is a Fn(usize, usize) -> T, we could just as well explicitly pass a & reference to it, and & references are Copy:

fn make<T, F>(n: usize, m: usize, f: F) -> Vec<T>
where
    F: Fn(usize, usize) -> T,
{
    let f_ref = &f;
    (0..m)
        .flat_map(|y| (0..n).map(move |x| f_ref(x, y)))
        .collect()
}

In this case, the closure takes its environment by value, and this environment is composed of y and f_ref, both of them being Copy, everything is well.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Levans
  • 14,196
  • 3
  • 49
  • 53
  • 2
    You could also write "fn make(n: usize, m: usize, ref f: F) -> Vec" to take a reference to the closure. – A.B. Jun 05 '15 at 07:56
  • Perfect! I got confused by the second error, I didn't realize it was talking about f... @A.B.: Nice! I keep forgetting about patterns in function arguments. – Gyscos Jun 05 '15 at 16:31
1

Adding to Levans's excellent answer, another way of defining the function would be

fn make<T, F>(n: usize, m: usize, f: F) -> Vec<T>
where
    F: Fn(usize, usize) -> T + Copy,
{
    (0..m).flat_map(|y| (0..n).map(move |x| f(x, y))).collect()
}

Since we know that |x, y| x + y is a Copy type, f would get copied for every callback that flat_map invokes. I would still prefer Levans's way as this would not be as efficient as copying a reference.

vikram2784
  • 783
  • 7
  • 14