Struggling with the subtyping relation of lifetimes in Rust

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.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)