3
votes

I'm trying to write a generic function that creates a default value and iterates through its internal "buffer." All this happens within the function with no arguments nor return values. I seem to have the lifetimes declared incorrectly and I am having a hard time setting them correctly for this to work.

Here is an example type that implements the Default and IntoIterator traits, as well as a new trait called Foo that requires one method.

trait Foo<T> {
    fn add(&mut self, T);
}

struct FooBar<T> {
    buf: Vec<Option<T>>,
    len: usize,
}

impl<T> FooBar<T> {
    fn new() -> Self {
        let buf = Vec::new();
        let len = 0;
        Self { buf, len }
    }

    fn iter(&self) -> FooBarIter<T> {
        FooBarIter { foo: self, pos: 0 }
    }
}

impl<T> Foo<T> for FooBar<T> {
    fn add(&mut self, val: T) {
        self.buf.push(Some(val));
        self.len += 1;
    }
}

impl<T> Default for FooBar<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<'a, T: 'a> IntoIterator for &'a FooBar<T> {
    type Item = &'a T;
    type IntoIter = FooBarIter<'a, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

FooBar just adds values to a vector which it owns. To only allow a non-consuming iteration, I defined an iterator which borrows the underlying Vec and maintains the "current" index during the iteration, returning a reference to each element which the consumer borrows.

struct FooBarIter<'a, T: 'a> {
    foo: &'a FooBar<T>,
    pos: usize,
}

impl<'a, T> Iterator for FooBarIter<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.foo.len <= self.pos {
            return None;
        }

        self.pos += 1;
        self.foo.buf[self.pos - 1].as_ref()
    }
}

A generic function creates a default value, adds some items (of type &str), then iterates though them by reference. I seem to have declared the lifetimes incorrectly because Rust complains that the borrowed value of FooBar doesn't live long enough. However, it says that it lives until the end of the function, so I'm confused as to how long Rust is expecting the borrow to actually live.

fn start<'a, T: 'a>()
where
    T: Foo<&'a str> + Default,
    &'a T: IntoIterator<Item = &'a &'a str>,
{
    let mut f = T::default();
    f.add("abcd");
    f.add("efgh");

    for val in &f {
        println!("{}", *val);
    }

    f.add("ijkl");

    for val in &f {
        println!("{}", *val);
    }
}

fn main() {
    start::<FooBar<&str>>();
}

Here is the error which states that that &f does not live long enough because the "borrowed value only lives until" the end of the function.

error[E0597]: `f` does not live long enough
  --> src/main.rs:70:17
   |
70 |     for val in &f {
   |                 ^ does not live long enough
...
73 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 61:1...
  --> src/main.rs:61:1
   |
61 | / fn start<'a, T: 'a>()
62 | | where
63 | |     T: Foo<&'a str> + Default,
64 | |     &'a T: IntoIterator<Item = &'a &'a str>,
...  |
72 | |     }
73 | | }
   | |_^

I've also tried setting a different lifetime parameter for the &'a IntoIterator trait, but then I end up going around in circles for other errors, not really getting close to setting the correct lifetimes.

To further illustrate what I'm trying to achieve with FooBar, here is an example of what I'm trying to do if FooBar was instead a Vec.

fn start() {
    let mut f = Vec::new();
    f.push("abcd");
    f.push("efgh");

    for val in &f {
        println!("{}", *val);
    }

    f.push("ijkl");

    for val in &f {
        println!("{}", *val);
    }
}

Am I even on the right track with trying to figure out the lifetime parameters, or is it something else entirely?

2

2 Answers

2
votes

I believe that what you are attempting is currently impossible.

To start with, your error message is because the caller is specifying the lifetime when passed to start:

fn start<'a, T: 'a>()

This doesn't make any sense because the caller cannot control the lifetime. For example, what if the call was start::<Vec<&'static str>> but the function created some temporary values? This lifetime is also tied to the iterator implementation — if the reference to the value lives as long as the caller dictates, then it can be iterated.

In many similar cases, higher-ranked trait bounds are the solution, however, the closest I can get is a bit different:

trait Foo<T> {
    fn add(&mut self, T);
}

impl<T> Foo<T> for Vec<T> {
    fn add(&mut self, val: T) {
        self.push(val);
    }
}

fn start<T>()
where
    T: Default + Foo<&'static str>,
    for<'a> &'a T: IntoIterator,
    for<'a, 'b> &'a <&'b T as IntoIterator>::Item: std::fmt::Display,
{
    let mut f = T::default();
    f.add("abcd");
    f.add("efgh");

    for val in &f {
        println!("{}", &val);
    }

    f.add("ijkl");

    for val in &f {
        println!("{}", &val);
    }
}

fn main() {
    start::<Vec<&str>>();
}

The main sticking point is T: Default + Foo<&'static str>. As far as what I can see, there's no way to pass in a concrete type for T that could use a HRTB defined by the function. In this case, we can use 'static. If we tried to use for <'a> T: Default + Foo<&'a str>, we get the error:

error[E0277]: the trait bound `for<'a> std::vec::Vec<&str>: Foo<&'a str>` is not satisfied
  --> src/main.rs:33:5
   |
33 |     start::<Vec<&str>>();
   |     ^^^^^^^^^^^^^^^^^^ the trait `for<'a> Foo<&'a str>` is not implemented for `std::vec::Vec<&str>`
   |
   = help: the following implementations were found:
             <std::vec::Vec<T> as Foo<T>>
   = note: required by `start`

The compiler doesn't know to match up the lifetime declared at the call site with the one specified by the HRTB. I believe, but am not sure, that generic associated types might address the problem.

0
votes

Thanks to Shepmaster for pointing me in the right direction with HRTBs. After reading up on the topic, I was able to have the function itself provide the lifetime of FooBarIter and FooBarIter::Item instead. FooBarIter can now be borrowed and last until the end of the function. The lifetime of FooBar can still be provided by the caller.

Here is the only change that was necessary for me to make this finally compile.

fn start<'a, T>()
where
    T: Foo<&'a str> + Default,
    for<'b> &'b T: IntoIterator<Item = &'b &'a str>,
{
    let mut f = T::default();
    f.add("abcd");
    f.add("efgh");

    for val in &f {
        println!("{}", *val);
    }

    f.add("ijkl");

    for val in &f {
        println!("{}", *val);
    }
}