Jon discussed the above code snippet, gen is a function that takes in a t which is a reference that lives AT LEAST'a and is of the unit type. It returns a concrete type that implements the trait Sized and lives AT LEAST'a.
So far so good.
But Jon says that the problem is that the lifetime annotations mean that t lives for AT LEAST'a which is NOT what the intent was; the intent was to annotate that t lives for AT MOST'a.
Jon then gives the solution as follows:
trait Captures<U> {}
impl<T: ?Sized, U> Captures<U> for T {}
fn gen<’a>(t: &’a ())
-> impl Sized + Captures<&‘a ()> {
t
}
I don't see how by adding Captures<&‘a ()>, it tells the compiler that now the gen function takes in a t that lives AT MOST'a.
When a type has a lifetime annotation, then this means that values of that type live for at most that lifetime. The captures trait essentially just adds a lifetime annotation, hence it does what you want.
The syntax + 'a means "this type has no lifetime annotations shorter than 'a". That is, the type cannot have an "at most" restriction shorter than 'a, so it lives for at least 'a.
In short, -> impl Trait + 'a is considered to be incorrect in the lifetime constraint:
-> impl Trait + 'a, the so-called 'outlives trick', means the opaque return type outlives 'a
but you actually should mean 'a outlives the opaque return type, which needs the 'capture trick' -> impl Sized + Captures<&'a ()>
...
Consider what impl Sized + 'a means. We're returning an opaque type and promising that it outlives any lifetime 'a.
This isn't actually what we want to promise. We want to promise that the opaque type captures some lifetime 'a, and consequently, that for the opaque type to outlive some other lifetime, 'a must outlive that other lifetime. If we could say in Rust that a lifetime must outlive a type, we would say that the 'a lifetime must outlive the returned opaque type.
That is, the promise we're making is the wrong way around.
It works anyway in this specific case only because the lifetime of the returned opaque type is exactly equal to the lifetime 'a. Because equality is symmetric, the fact that our promise is the wrong way around doesn't matter.
...
I love your reply because it is concise, answers the question and straight to the point.
I see that there is some contravariance/covariance stuff here pertaining to where the annotation is placed, on the struct vs. in the opaque return type. Or am I mistaken?
I'm afraid I don't have a proper citation; I don't believe an official one (which directly discusses this) exists.
RFC 1214 has a lot of discussion about projections, but invariance is barely mentioned.
However, if you think about a an unnormalized projection Trait::<'a>:::Ty, it can't be anything but invariant in this form, because these are all valid implementations.
impl<'a> Trait<'a> for Covariant {
type Ty = &'a str;
}
impl<'a> Trait<'a> for Contravariant {
type Ty = fn(&'a str);
}
impl<'a> Trait<'a> for Invariant {
type Ty = &'static mut &'a String;
}
impl<'a> Trait<'a> for Bivariant {
type Ty = String;
}
If you can normalize it for a specific implementation, you can "recover" the underlying variance. Now the missing piece with RPIT or TAIT is, does the compiler (internally) normalize these opaque types? Again I don't have a citation, but as far as my experiments have gone, it seems like it does not.[1]
Is it feasible in the future and, if so, would it be entertained by the relevant teams? I also don't know.