GATs: Replacing &S with Deref<Target = S> breaks the code, why?

I have the following trait with a GAT:

pub trait Foo: {
    type Bar<'a> where Self: 'a;
}

The following impl does compile:

impl<S> Foo for &S
    where S: Foo
{
    type Bar<'a> = S::Bar<'a> where Self: 'a;
}

Now I wanted something more generic. So I tried the following, which looks identical
except I replaced &S with Deref<Target = S>:

use std::ops::*;

impl<T, S> Foo for T
    where T: Deref<Target = S>, S: Foo
{
    type Bar<'a> = S::Bar<'a> where Self: 'a;
}

But it does not compile. I get:

error[E0309]: the parameter type `S` may not live long enough

What is the reason and how can I fix this?

The reason for that i related to the fact that lifetime GATs are, implicitly, HRBTs. And this leads to tons of the issues.

In your case fact that S implements Foo is not enough. You also need S to live as long as T… but it's not clear how you can specify that limit.

Limited solution which works today is to use something like this:

impl<T, S> Foo for T
    where T: Deref<Target = S>, S: Foo + 'static
{
    type Bar<'a> = S::Bar<'a> where Self: 'a;
}

This works, but is, obviously, limited.

The hope is that eventually Rust would become more flexible, but no one knows when and if this may happen.

1 Like

So the reason why the first version works is that the compiler automatically knows that S lives as long as &S?

Why can this not be somehow specified for S and T in the where ... part? Would such a possibility not also be useful even before GATs were introduced?

Because compiler doesn't support that?

Before GATs stabilization HRBTs were something you use very rarely and usually it wasn't a big problem. GATs are implicitly HRBTs which means this problem becomes more important. But it's so very well-known that it's even explicitly mentioned as a problem in the very blog post which declared GATs stable!

Basically as was said before: This is a bug. And one that is fixable in an almost certainly backwards-compatible manner. But it’s also tough to fix. So it’s not something we want to block stabilization on.

I kind of understand why it was done that way: even with this limitation GATs are very useful. But yeah, that's something literally everyone who tries to use lifetime GATs hit pretty quickly.

1 Like

That linked blog post goes into detail about the exact kinds of bounds Rust currently lacks the ability to express. Your case is a little bit different, but if we apply the syntax from that post you might get something like

impl<T, S> Foo for T
where
    T: Deref<Target = S>,
    S: Foo,
    for<'a where Self::Bar<'a>:> S: 'a,
{
    type Bar<'a> = S::Bar<'a> where Self: 'a;
}

Here for<'a where Self::Bar<'a>:> S: 'a, would translate roughly as "S must be valid for any lifetime 'a which is used with the associated type Self::Bar<'a>".

This use case might be sufficiently different from the motivating examples in that blog post that it would require a different feature in the compiler though. I'm not very familiar with the inner workings of HRTBs.

1 Like