Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetime issue when mapping an iterator over elements of a matrix

My aim is to retrieve an iterator over all elements in a matrix alongside the row number associated with each element.

The following is a simplified version of the lifetime issue i'm running into.

fn main() {

    let mat = [ [1i32, 2,    3],
                [4,    5,    6],
                [7,    8,    9] ];

    // Create an iterator that produces each element alongside its row number.
    let all_elems = mat.iter().enumerate().flat_map(|(row, arr)| {
        arr.iter().map(|elem| (row, elem)) // Error occurs here.
    });

    for (row, elem) in all_elems {
        println!("Row: {}, Elem: {}", row, elem);
    }

}

Here's the error I'm getting:

<anon>:10:9: 10:43 error: cannot infer an appropriate lifetime for lifetime parameter 'r in function call due to conflicting requirements
<anon>:10         arr.iter().map(|elem| (row, elem))
                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:10:24: 10:42 note: first, the lifetime cannot outlive the expression at 10:23...
<anon>:10         arr.iter().map(|elem| (row, elem))
                                 ^~~~~~~~~~~~~~~~~~
<anon>:10:24: 10:42 note: ...so type `|&i32| -> (uint, &i32)` of expression is valid during the expression
<anon>:10         arr.iter().map(|elem| (row, elem))
                                 ^~~~~~~~~~~~~~~~~~
<anon>:10:9: 10:43 note: but, the lifetime must be valid for the method call at 10:8...
<anon>:10         arr.iter().map(|elem| (row, elem))
                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:10:24: 10:42 note: ...so that argument is valid for the call
<anon>:10         arr.iter().map(|elem| (row, elem))
                                 ^~~~~~~~~~~~~~~~~~

Here's the playpen link.

The issue seems to stem from an inability to infer the lifetime in the map method's closure argument, though I'm unsure why.

  • Can someone explain the issue here a little more clearly?
  • Is it possible to produce the desired iterator another way?
like image 270
mindTree Avatar asked Nov 16 '14 07:11

mindTree


2 Answers

Even if it is not very clear, the compiler can't figure out the lifetime of your inner closure

|elem| (row, elem)

Because this closure captures row from its environment (here it's the body of your outer closure), thus should not be able to outlive it.

Yet, you are trying to return it wrapped into the Map<> object returned by .map(..), and so have conflicting requirements: your inner closure is asked to outlive a scope it can't outlive !

A simple way to escape this issue is to make your inner closure take row as an argument as well, and to do so, we can make use of:

  • repeat(..) which creates an iterator repeating the same item forever
  • .zip(..) method of iterators, which allow to advance two iterators at the same time

In order to something in this way:

let mut all_elems = mat.iter().enumerate().flat_map(|(row, arr)| {
    arr.iter()
       .zip(repeat(row))
       .map(|(elem, my_row)| (my_row, elem))
});

But in this case, we can make it even more simple as |(elem, my_row)| (my_row, elem) looks pretty useless:

let mut all_elems = mat.iter().enumerate().flat_map(|(row, arr)| {
    repeat(row).zip(arr.iter())
});
like image 64
Levans Avatar answered Nov 15 '22 07:11

Levans


Another way to increase the lifetime of the inner closure |elem| (row, elem) would be to mark it as a moving closure by simply adding the move keyword. This will also compile:

let all_elems = mat.iter().enumerate().flat_map(|(row, arr)| {
    arr.iter().map(move |elem| (row, elem))
});

The same behavior can also be observed when trying to return a (boxed) closure from a function. The following function fails to compile because the lifetime of the closure is bound by the lifetime of the local variable row:

fn foo_fail() -> Box<Fn(u32) -> (u32, u32)> {
    let row = 1;
    Box::new(|elem| (row, elem))
}

Using a moving closure instead will work fine:

fn foo_success() -> Box<Fn(u32) -> (u32, u32)> {
    let row = 1;
    Box::new(move |elem| (row, elem))
}
like image 30
Philipp Hartwig Avatar answered Nov 15 '22 07:11

Philipp Hartwig