Bounding GATs to a concrete struct

I have been playing around with Rust traits, and I have hit a roadblock which I don't see how to solve. The problem is a bit convoluted, so I would try to explain it as simply as a I can.

My code is organized using general traits of the form "Trait that returns some specific Trait as an AT" like this:

trait SomeTrait {
    type Type: TypeTrait;

    fn gimme(&self) -> Self::Type;

And in some cases I have more specific traits that return concrete types:

trait SpecificTrait: SomeTrait<Type = TypeStruct> {

So far, so good. However, sometimes the return type carries data from the original struct, so I need to use GATs to express the trait:

trait AnotherTrait {
    type Type<'a> where Self: 'a;

    fn lendme<'l>(&'l self) -> Self::Type<'l>;

And so far I have been unable to express the same constraint on GAT as I did with regular AT. Of course this doesn't compile:

trait AnotherSpecificTrait: AnotherTrait<Type<'a> = TypeStructWithLifetime<'a>> {

Because the lifetime a' doesn't exists in that context. I have tried using a where statement like this:

trait AnotherSpecificTrait: AnotherTrait
    for<'a> <Self as AnotherTrait>::Type<'a> = TypeStructWithLifetime<'a>,

Which works if the bound is another trait but not if it is a struct, because the compiler doesn't allow to use = in a where clause.

At this point I have run out of ideas, and I wonder if it is possible to express the idea of "a Trait that restricts the GAT of another Trait to a specific struct" and if there is a good reason this is not allowed.

For lifetimes, this should be possible relatively straightforwardly; unlike when the GAT argument is a type.

IIRC the syntax goes something like

trait AnotherSpecificTrait: for<'a> AnotherTrait<Type<'a> = TypeStructWithLifetime<'a>> {

Edit: Tested it… looks like the Self: 'a does complicate some things. If in your use case, Self: 'static isn’t overly restrictive, then

trait AnotherSpecificTrait:
    'static + for<'a> AnotherTrait<Type<'a> = TypeStructWithLifetime<'a>>

does compile. Otherwise… I’m not sure yet if there’d be a good solution.

Thanks a lot. I didn't realize you could introduce a for in the trait declaration (actually, I don't think I have seen a for clause there before). The 'static lifetime is indeed too restrictive, but that's not a problem with the trait declaration.

If I force the GAT not to have a Self: 'a bound everything works as expected.

I have been tinkering a bit more with the code, and it seems there is no easy way to remove the where Self: 'a bound from the GAT.

I found a workaround by adding a method to the trait which uses the GAT but not the object itself like in this post, which seems brittle but I've seen no problems so far.

The compiler error points to this comment which does lay out a workaround :wink:

  = note: this bound is currently required to ensure that impls have maximum flexibility
  = note: we are soliciting feedback, see issue #87479 <> for more information

This breaks my code. Workaround?

First, if any code breaks from adding the required bounds, we really want feedback. Second, the workaround is to move the GAT into a super trait. Using the example above, our new code would look like:

trait IterableSuper {
    type Item<'x>;
trait Iterable: IterableSuper {
    fn iter<'a>(&'a self) -> Self::Item<'a>;

Looking through the discussion this comment already reports an (at least somewhat) similar-looking code example. I haven’t read through all the answers though :slight_smile:

Again, thanks a lot for your time.

Indeed, separating the base trait into a TypeTrait and a MethodTrait like this:

trait TypeTrait {
    type Type<'a>: TypeStructTrait<'a>;

trait MethodTrait: TypeTrait {
    fn lendme<'l>(&'l self) -> Self::Type<'l>;

Removes the Self: 'a bound. Now the super Trait has the expected bounds:

trait SuperTrait: MethodTrait + for<'a> TypeTrait<Type<'a> = SpecificStruct<'a>> {}

I did not participate in the discussion about GATs, and the more I look at it the less I understand the need for the Self bound. So far the borrow checker seems to understand that whatever lendme() returns is only valid as far as the lender is alive and unchanged.

As far as I know GATs were eventually stabilized even though some outstanding usability issues remained. But the benefits of stabilizing were seen higher than the downsides of still having remaining issues, and there were no known shortcomings that couldn’t be fixed at a later time in a backwards-compatible manner.

The desire to help the user not to forget useful Self: 'a-style bounds (which ideally never should have a downside) is what motivates these errors, not any concerns of e.g. soundness that would actually make them necessary. I believe, AFAIR, the motivation to have it an error is that this way it’s still easy to relax the constraints in the future, rather than going the opposite way of turning something that works into an error. This would be thus in line with the approach of stabilizing them in a way that doesn’t preclude any future fixes around the remaining usability issues.

which ideally never should have a downside

for example in the code at hand, arguable the actual issue is that the for<'a> … bound doesn’t work. It should arguably be the case that either implicitly, or with some explicit syntax, this for<'a> trait bound can be limited to only quantify over lifetimes fulfilling Self: 'a.

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.