Restricting lifetime of a GAT

I am trying to adapt a generic associated type with a lifetime parameter (Item in LendingIterator in the example) to an associated type that does not a have lifetime parameter. I would expect this to work if the lifetime of the GAT is 'static. Is there a way to restrict the GAT to make this example compile?

pub trait LendingIterator {
    type Item<'a> where Self: 'a;
    fn next_borrowed(&mut self) -> Option<Self::Item<'_>>;
}

struct Adapter<I>(I);

impl<T: LendingIterator + 'static> Iterator for Adapter<T> where T::Item<'static>: 'static {
    type Item = T::Item<'static>;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next_borrowed()
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
  --> src/lib.rs:11:9
   |
10 |     fn next(&mut self) -> Option<Self::Item> {
   |             - let's call the lifetime of this reference `'1`
11 |         self.0.next_borrowed()
   |         ^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'static`

error: could not compile `playground` due to previous error

// Definition of LendingIterator's next_borrowed:
// LendingIterator::next_borrowed::<'lending>(&'lending Self) -> Option<Self::Item<'lending>>

// desugar this call:
// LendingIterator::next_borrowed::<'lending>(&'lending self.0) -> Option<T::Item<'lending>>
self.0.next_borrowed()

When 'lending is 'static, it requires &'static self.0 (i.e. &'static self), which is impossible.

I think you need to be able to say that for<'a> T::Item<'a>: 'static, but I haven't been able to find a way to make that work either after playing around with it a bit

So… this compiles:

pub trait LendingIterator {
    type Item<'a>
    where
        Self: 'a;
    fn next_borrowed(&mut self) -> Option<Self::Item<'_>>;
}

struct Adapter<I>(I);

impl<Item, T: 'static + for<'a> LendingIterator<Item<'a> = Item>> Iterator for Adapter<T>
where
    T::Item<'static>: 'static,
{
    type Item = Item;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next_borrowed()
    }
}

struct Demonstration<T>(std::vec::IntoIter<T>);
impl<T> LendingIterator for Demonstration<T> {
    type Item<'a> = T
    where Self: 'a;
    fn next_borrowed(&mut self) -> Option<Self::Item<'_>> {
        self.0.next()
    }
}

fn demonstration() {
    let x = 1;
    let y = 2;
    let z = 3;
    let d = Adapter(Demonstration(vec![1, 2, 3].into_iter()));
    for v in d {
        println!("{v}");
    }
}

But real lifetime GATs are still kind-of weak… the “fake” GATs that have been stable for a while work better, and allow to avoid the T: static bound entirely; on the other hand, every way I tried so far with the real GATs ran into errors claiming “due to current limitations in the borrow checker, this implies a `'static` lifetime”.

pub trait GatLessLendingIterator: for<'a> HasGatLessLendingIteratorItem<'a> {
    fn next_borrowed(&mut self) -> Option<GatLessLendingIteratorItem<'_, Self>>;
}
pub trait HasGatLessLendingIteratorItem<'a, _Bound = &'a Self> {
    type Item;
}
pub type GatLessLendingIteratorItem<'a, Self_> = <Self_ as HasGatLessLendingIteratorItem<'a>>::Item;

struct Adapter<I>(I);

impl<Item, T: GatLessLendingIterator> Iterator for Adapter<T>
where
    T: for<'a> HasGatLessLendingIteratorItem<'a, Item = Item>,
{
    type Item = Item;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next_borrowed()
    }
}

struct Demonstration<T>(std::vec::IntoIter<T>);

impl<T> GatLessLendingIterator for Demonstration<T> {
    fn next_borrowed(&mut self) -> Option<GatLessLendingIteratorItem<'_, Self>> {
        self.0.next()
    }
}
impl<'a, T> HasGatLessLendingIteratorItem<'a> for Demonstration<T> {
    type Item = T;
}

fn demo1<T>(x: Adapter<Demonstration<T>>) {
    for _ in x {}
}

fn demo2() {
    let x = 1;
    let y = 2;
    let z = 3;
    let d = Adapter(Demonstration(vec![&x, &y, &z].into_iter()));
    for v in d {
        println!("{v}");
    }
}

The downside of course is that the impls of such “fake GAT” traits is more verbose.

4 Likes

steffahn Thank you very much for the working solution! I would not have expected that I can and have to use for<'…> in that place.

Would this also work with a non-static lifetime?

The GAT-less solution appears to be quiet complex. I first need to understand it.

As I mentioned in my previous response, I'm unable to find a way to avoid the T: 'static bound, so it might be impossible to avoid with a real GAT.

If you want to learn more about GAT workarounds, I can recommend this article: The Better Alternative to Lifetime GATs - Sabrina Jewson

Thanks as well for the article. I am afraid that is too complicated for my use case.

Luckily I do not need mutable access the object that has the GAT and hence (for reasons unclear to me) I am able to access the non-'static data through a pointer. My implementation now looks closer to this:

This unfortunately does not allow for owning iterators.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.