Disclaimer: I am not exactly a CS guru, so this answer will focus on practical concepts and I will not even attempt to link it to theoretical concepts lest I make a mess of things.
I think that the issue is trying to apply the subtyping concept to something which is not a type.
'a
is a lifetime&'a T
is a type
You can compare &'a T
and &'b U
and see whether they obey a subtyping relationship, but you cannot establish a subtyping relationship with two lifetimes in the abstract because:
- sometimes, in order to be substituable, the new lifetime must be greater than the replaced lifetime.
- sometimes, in order to be substituable, the new lifetime must be smaller than the replaced lifetime.
We can check this through two simple examples.
The first example is maybe the easiest: a lifetime can be substituted if it is greater!
// Using a lifetime as a bound
struct Reference<'a, T>
where T: 'a
{
data: &'a T
}
fn switch<'a, 'b, T>(r: &mut Reference<'a, T>, new: &'b T)
where 'b: 'a
{
r.data = new;
}
Here, the compiler only allows the substitution if 'b
is at least as great as 'a
which is expressed by the lifetime bound 'b: 'a
. This is because Rust abhors dangling references, and thus a container may only contain references to objects that will outlive it.
When used as a guarantee, a greater lifetime is a subtype of a lesser lifetime and can be substituted in its stead. This hints as mentioned by @aturon, that in this usage 'static
is a subtype of all lifetimes.
The second example is a tad trickier: a lifetime can be substituted if it is lesser!
Let’s start with the following:
struct Token;
fn restrict<'a, 'b, T>(original: &'a T, _: &'b Token) -> &'b T
where 'a: 'b
{
original
}
The following usage is correct:
fn main() {
let i = 4;
{
let lesser = Token;
let k = restrict(&i, &lesser);
println!("{}", k);
}
}
And our previous demonstration said that we can substitute a greater lifetime instead of a lesser one:
fn main() {
let greater = Token;
let j; // prevent unification of lifetimes
{
let i = 4;
j = restrict(&i, &greater);
}
println!("{}", j);
}
error: `i` does not live long enough
j = restrict(&i, &greater);
When used as a constraint, a lesser lifetime is a subtype of a greater lifetime and can be substituted in its stead. In this usage, 'static
is a supertype of all lifetimes.
Therefore, there is no single subtyping relationship between lifetimes because they serve two radically opposite purposes!
To recapitulate:
- when used as a guarantee:
greater <: lesser
- when used as a constraint:
lesser <: greater
Note: some lifetime can plausibly act both as a guarantee AND a constraint at the same time.