GAT lifetime issue

Hello Everyone,

I recently started playing with GATs, but I ran into a lifetime issue which, after couple of evenings, I don't seem to be able to fix myself. I'll probably start with a minimized example:

#![feature(generic_associated_types)]

struct ColorDisplay<Ifc> {
    ifc: Ifc,
}

struct ColorDisplayDataIter<'a, Ifc> {
    ifc: &'a mut Ifc,
}
impl<'a, Ifc> Iterator for ColorDisplayDataIter<'a, Ifc> {
    type Item = u16;
    fn next(&mut self) -> Option<Self::Item> {
        None
    }
}

//

trait ReadData {
    type Iter<'a>: Iterator<Item = u16>;
    fn read<'a>(&'a mut self) -> Self::Iter<'a>;
}

impl<Ifc> ReadData for ColorDisplay<Ifc> {
    type Iter<'a> = ColorDisplayDataIter<'a, Ifc>;
    fn read<'a>(&'a mut self) -> Self::Iter<'a> {
        ColorDisplayDataIter { ifc: &mut self.ifc }
    }
}

This fails with

error[E0309]: the parameter type `Ifc` may not live long enough
  --> src/main.rs:26:5
   |
25 | impl<Ifc> ReadData for ColorDisplay<Ifc> {
   |      --- help: consider adding an explicit lifetime bound...: `Ifc: 'a`
26 |     type Iter<'a> = ColorDisplayDataIter<'a, Ifc>;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `Ifc` will meet its required lifetime bounds

I basically understand where the error is coming from, but it seems to be impossible to constrain Ifc by the lifetime 'a.
I don't want to add the Ifc type parameter to the ReadData trait as Ifc is an implementation detail of any Display that will implement the ReadData trait.
Is this caused by the current state of GAT implementation or will this still be impossible once GATs land in stable.
Or I might be completely misunderstanding something :slight_smile:

You only use Iter<'a> in a function with &'a mut self argument,

so you can add a Self: 'a bound.

    type Iter<'a>: Iterator<Item = u16> where Self: 'a;

and

    type Iter<'a> where Self: 'a = ColorDisplayDataIter<'a, Ifc>;

Rust Playground

1 Like

Thanks a ton, that works like a charm.

Note that generic associated types that are only generic over lifetime parameters (so not over type parameters) can be encoded using ordinary traits in stable rust:

// Only used with MutRef == &'a mut Self
// The &'a mut Self parameter gives rise to an implicit Self: 'a bound,
// which is important for the HRTB below to work.
trait HasReadDataIter<MutRef> {
    type Iter: Iterator<Item = u16>;
}

// Higher-ranked trait bound (HRTB) as supertrait. Implicit bound from the &'a mut Self
// means this only ranges over 'a with Self: 'a
trait ReadData: for<'a> HasReadDataIter<&'a mut Self> {
    fn read<'a>(&'a mut self) -> <Self as HasReadDataIter<&'a mut Self>>::Iter;
}

impl<'a, Ifc> HasReadDataIter<&'a mut Self> for ColorDisplay<Ifc> {
    type Iter = ColorDisplayDataIter<'a, Ifc>;
}

impl<Ifc> ReadData for ColorDisplay<Ifc> {
    fn read<'a>(&'a mut self) -> <Self as HasReadDataIter<&'a mut Self>>::Iter {
        ColorDisplayDataIter { ifc: &mut self.ifc }
    }
}
2 Likes

It's cool that that snippet works, and if I needed to build something with that I'd be glad I could do it in stable today. But let's be honest: that really obscures intent.

I had a hunch this could be solved with just lifetime HRTB, but so far I only used them like twice.
Now that I look at your example, it makes perfect sense, but I wouldn't come up with this myself with my current (obviously very limited :slightly_smiling_face:) understanding of HRTBs.

I'll probably use it, but I must agree with @jjpe that the GAT version seems to better describe the intent, so probably switch to it once GATs hit stable.

Thanks again, you saved me another evening headache.

To make this more usable, an extra lifetime argument and a default for the MutRef parameter help

trait HasReadDataIter<'a, MutRef = &'a mut Self> {
    type Iter: Iterator<Item = u16>;
}

trait ReadData: for<'a> HasReadDataIter<'a> {
    fn read<'a>(&'a mut self) -> <Self as HasReadDataIter<'a>>::Iter;
}

impl<'a, Ifc> HasReadDataIter<'a> for ColorDisplay<Ifc> {
    type Iter = ColorDisplayDataIter<'a, Ifc>;
}

impl<Ifc> ReadData for ColorDisplay<Ifc> {
    fn read<'a>(&'a mut self) -> <Self as HasReadDataIter<'a>>::Iter {
        ColorDisplayDataIter { ifc: &mut self.ifc }
    }
}

You can also add a type synonym

type ReadDataIter<'a, Self_> = <Self_ as HasReadDataIter<'a>>::Iter;

and use it

trait ReadData: for<'a> HasReadDataIter<'a> {
    fn read<'a>(&'a mut self) -> ReadDataIter<'a, Self>;
}

impl<'a, Ifc> HasReadDataIter<'a> for ColorDisplay<Ifc> {
    type Iter = ColorDisplayDataIter<'a, Ifc>;
}

impl<Ifc> ReadData for ColorDisplay<Ifc> {
    fn read<'a>(&'a mut self) -> ReadDataIter<'a, Self> {
        ColorDisplayDataIter { ifc: &mut self.ifc }
    }
}

And finally, you can use lifetime elision on the read method:

trait ReadData: for<'a> HasReadDataIter<'a> {
    fn read(&mut self) -> ReadDataIter<'_, Self>;
}

impl<'a, Ifc> HasReadDataIter<'a> for ColorDisplay<Ifc> {
    type Iter = ColorDisplayDataIter<'a, Ifc>;
}

impl<Ifc> ReadData for ColorDisplay<Ifc> {
    fn read(&mut self) -> ReadDataIter<'_, Self> {
        ColorDisplayDataIter { ifc: &mut self.ifc }
    }
}

Rust Playground

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.