Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a program compile despite an apparent lifetime mismatch?

Tags:

rust

lifetime

Given the following Rust program:

struct Value<'v>(&'v ());
struct Container {}

impl Container {
    fn get<'v>(&'v self) -> Value<'v> {
        todo!()
    }
    
    fn set<'v>(&'v self, x: Value<'v>) {
        todo!()
    }
}

fn convert<'v1, 'v2>(x: &'v1 Container, env: &'v2 Container) {
    let root: Value<'v2> = env.get();
    x.set(root);
}

I would expect convert to be a compile time error as Value<'v2> gets passed to x.set() which requires a value of type Value<'v1> - but it successfully compiles. There is no subtyping relationship between 'v1 and 'v2. How has Rust inferred satisfying lifetimes?

like image 795
Neil Mitchell Avatar asked Jun 30 '20 14:06

Neil Mitchell


1 Answers

The compiler is always allowed to re-borrow with a shorter lifetime.

In this case, what happens in:

fn convert<'v1, 'v2>(x: &'v1 Container, env: &'v2 Container) {
    let root: Value<'v2> = env.get();
    x.set(root);
}

Is that the compiler reborrows x (aka (&*x)) with a lifetime 'v3, shorter than 'v2, which is allowed due to the (inferred) variance of Value<'v> (which matches &'v T).


It is possible to change the inferred variance of Value<'v> by changing the inner value:

  • &'v () (the current) is covariant.
  • Cell<&'v ()> is invariant.
  • fn (&'v ()) -> () is contravariant, the inverse of covariant.

Using an invariant Value<'v> prevents unifying the lifetime with a fresh one, while using a contravariant Value<'v> only allows unifying the lifetime with a greater one.

like image 140
Matthieu M. Avatar answered Oct 31 '22 03:10

Matthieu M.