3

I have a generic function which is called recursively and which can take arguments that can be converted into an iterator.

I would like to chain the iterator which is passed as argument with the iterator created from a function local collection. The idea is that after calling the recursive function several times a single iterator is created which is able to iterate over all the collections living in their individual stack frame.

fn func_taking_iter<'a, I>(vals: I) -> ()
where
    I: IntoIterator<Item = &'a u32>,
    I::IntoIter: Clone,
{
    let vals = vals.into_iter();
    let mut new_val: [u32; 1] = [0u32; 1];
    for x in vals.clone() {
        new_val[0] = *x;
    }

    new_val[0] += 1u32;
    if new_val[0] == 10u32 {
        for x in vals {
            println!("Value {}", *x);
        }
        return;
    }

    let res = vals.into_iter().chain(new_val.into_iter());

    func_taking_iter(res);
}

fn main() {
    let first_val = [0u32; 1];
    func_taking_iter(first_val.iter());
}

Unfortunatly I get the following error when I try to compile the code.

error[E0597]: `new_val` does not live long enough                                                 
  --> src\main.rs:20:38
   |
1  | fn func_taking_iter<'a, I>(vals: I) -> ()
   |                     -- lifetime `'a` defined here
...
20 |     let res = vals.into_iter().chain(new_val.into_iter());
   |               -----------------------^^^^^^^-------------
   |               |                      |
   |               |                      borrowed value does not live long enough
   |               argument requires that `new_val` is borrowed for `'a`
...
23 | }
   | - `new_val` dropped here while still borrowed

I don't have that much experience with Rust and I'm stuck here but in my opinion this doesn't seem like an impossible thing to do...


Edit: Improved example code so that only the iterator and not the whole collection is cloned thanks to the hint in Stargateur's answer

1 Answers1

2

You bound the lifetime of your new array to the lifetime 'a that life outside your function, it's a limitation of the lifetime system I don't think you can fix it today, maybe in the future it will compile. But even if the you could fix the lifetime problem you will end up with a "overflow evaluating the requirement" (E0275]). The compiler need to infer a infinite number of function because there is a infinite call to this function with infinite chain structure.

I don't advice to do this kind of code in rust, it's not efficient to chain so many iterator. I think you should change your code to use a vector. This will save you stack space because anyway your function is not tail recursive so you can end with a stack overflow problem.

fn func_taking_iter_aux(vals: Vec<u32>) -> Vec<u32> {
    let new_val = vals.last().copied().unwrap_or(0) + 1;

    if new_val == 10u32 {
        vals
    } else {
        let mut vals = vals;
        vals.push(new_val);
        func_taking_iter_aux(vals)
    }
}

fn func_taking_iter<'a, I>(vals: I) -> Vec<u32>
where
    I: IntoIterator<Item = &'a u32>,
{
    func_taking_iter_aux(vals.into_iter().copied().collect())
}

fn main() {
    let vals = func_taking_iter(Some(&0));
    for x in vals {
        println!("Value {}", x);
    }
}

I tried to keep your requirement as much as possible. This is now a tail recursive function.


Note that vals.clone() in your code clone all data and not just the iterator, you should have do let vals = vals.into_iter() and then you can clone the iterator and of course remove I: Clone,.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • Can you explain in a bit more detail what causes "the overflow evaluating the requirement" (E0275]) error in my specific case? I understand it in this scenario https://stackoverflow.com/questions/31196422/what-does-overflow-evaluating-the-requirement-mean-and-how-can-i-fix-it but I can't see the same scheme here. – clockworkman Dec 27 '19 at 13:38
  • @clockworkman you used static dispatch so real type at compile time, so generic `I` is in the first call (simplified) `Slice1` then it's `Chain` then it's `Chain, Slice3>` then `Chain, Slice3>, Slice4>` etc etc there is no end, that could be fix using dyn dispatch I think but again it's a very bad idea – Stargateur Dec 27 '19 at 14:08