Pamplemousse
Pamplemousse

Reputation: 174

Rust: error[E0495]: cannot infer an appropriate lifetime for autorefdue to conflicting requirements in closure

Raw idea

In a dummy project I have, I would like to use cycling iterators (to generate integers for example).

use std::iter::Cycle;

type IntegerCycle = Cycle<std::slice::Iter<'static, i32>>;

fn generate_cycles() -> [IntegerCycle; 2] {
  let mut cycles = [
    [1, 2].iter().cycle(),
    [2, 4].iter().cycle(),
  ];

  cycles
}


fn main() {
  let mut cycles = generate_cycles();
  // ...
}

Refactor

Although the previous piece of code works as intended, my real world example is a bit more complicated, so I am looking to adapt the generate_cycles function to be able to perform more operations (in the following example, multiply by 2, then generate the cycling iterators). For this, I tried to use arraymap:

extern crate arraymap;

use arraymap::ArrayMap;
use std::iter::Cycle;

type IntegerCycle = Cycle<std::slice::Iter<'static, i32>>;

fn generate_cycles() -> [IntegerCycle; 2] {
  let mut cycles = [
    [1, 2],
    [2, 4],
  ];

  cycles
    .map(|points| {
      points.map(|point| point*2)
    })
    .map(|points| {
      points.iter().cycle()
    })
}


fn main() {
  let mut cycles = generate_cycles();
  // ...
}

The problem

The above solution does not work, and, as a Rust beginner recently exposed to the concept of "lifetime", I do not understand why the compiler is complaining here, or what I can do to make him happy.

error[E0495]: cannot infer an appropriate lifetime for autorefdue to conflicting requirements
  --> src/main.rs:20:14
   |
20 |       points.iter().cycle()
   |              ^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 19:10...
  --> src/main.rs:19:10
   |
19 |       .map(|points| {
   |  __________^
20 | |       points.iter().cycle()
21 | |     })
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:20:7
   |
20 |       points.iter().cycle()
   |       ^^^^^^
   = note: but, the lifetime must be valid for the static lifetime...
   = note: ...so that the expression is assignable:
           expected [std::iter::Cycle<std::slice::Iter<'static, i32>>; 2]
              found [std::iter::Cycle<std::slice::Iter<'_, i32>>; 2]

Here is a REPL with the code trying to make use of the arraymap: https://repl.it/repls/ShadowyStrikingFirm .

Upvotes: 1

Views: 183

Answers (1)

rodrigo
rodrigo

Reputation: 98398

In your type declaration:

type IntegerCycle = Cycle<std::slice::Iter<'static, i32>>;

You say that you the underlying slices you use to build your iterators must have 'static lifetime, that is, they must live forever. Then you use literal arrays such as [1, 2] that, as all literals, have 'static' lifetime and all goes well:

let r: &'static [i32; 2] = &[1, 2]; //Ok

But then, you try a code similar to this simpler one:

let a = [1, 2].map(|x| 2 * x);
let r: &'static [i32; 2] = &a; //error: borrowed value does not live long enough

That is the result of arraymap::map is a normal array, not a literal one, so it does not have a 'static lifetime. It cannot be static because you are computing the values in runtime. It will live as long as necessary, in my case as long as the variable a.

In your case, since the returns of arraymap::map are not assigned to variables, they are temporary values and they are quickly dropped. But even if you assigned it to a local variable, you could not return a reference to it, because the local variable is dropped when the function ends.

The solution is to return an iterator that owns the value. Something like this works:

type IntegerCycle = Cycle<std::vec::IntoIter<i32>>;

fn generate_cycles() -> [IntegerCycle; 2] {
  let cycles = [
    [1, 2],
    [2, 4],
  ];
  cycles
    .map(|points| {
      points.map(|point| point*2)
    })
    .map(|points| {
      points.to_vec().into_iter().cycle()
    })
}

Unfortunately you have to use a Vec instead of an array, because there is not an IntoIterator implementation for arrays, (there are for slices, but they do not own the values).

If you want to avoid the extra allocation of Vec you can use the arrayvec crate that does allow to take an iterator to an array:

type IntegerCycle = Cycle<arrayvec::IntoIter<[i32; 2]>>;

fn generate_cycles() -> [IntegerCycle; 2] {
  let cycles = [
    [1, 2],
    [2, 4],
  ];

  cycles
    .map(|points| {
      points.map(|point| point*2)
    })
    .map(|points| {
        let a = arrayvec::ArrayVec::from(*points);
        a.into_iter().cycle()
    })
}

NOTE: It looks like there is an attempt to add a proper IntoIterator impl for arrays by value to the std, but there are still some pending issues.

Upvotes: 1

Related Questions