Advanced Lifetimes and Borrowing

Variance

Variance describes how subtyping between two types or lifetimes (Sub <: Super, such as 'long: 'short) translates into subtyping between composite types parameterized by them (F<Sub> vs. F<Super>).

The Three Kinds of Variance

Given 'long: 'short (meaning 'long is a subtype of 'short) and Sub <: Super (for example, &'long T <: &'short T), a type constructor F<T> is:

  1. Covariant over T: Preserves the subtyping direction (F<Sub> <: F<Super>). You can pass F<&'long str> where F<&'short str> is expected.
  2. Invariant over T: No subtyping relationship exists in either direction. F<&'long str> and F<&'short str> cannot be substituted for one another.
  3. Contravariant over T: Reverses the subtyping direction (F<Super> <: F<Sub>). You can pass F<&'short str> where F<&'long str> is expected.
Subtyping Input: 'long <: 'short  (so &'long T <: &'short T)

1. Covariant (e.g. &'a T, Vec<T>, Box<T>):
   Vec<&'long T>  <:  Vec<&'short T>    (Direction preserved)

2. Invariant (e.g. &'a mut T, Cell<T>, RefCell<T>):
   &mut &'long T  !=  &mut &'short T    (No conversion allowed either way)

3. Contravariant (e.g. fn(T)):
   fn(&'short T)  <:  fn(&'long T)      (Direction reversed)

Variance Reference Table for Standard Types

Type Constructor Variance in 'a Variance in T Variance in U
&'a T Covariant Covariant —
&'a mut T Covariant Invariant —
Box<T>, Vec<T>, Rc<T>, Arc<T> — Covariant —
*const T, NonNull<T> — Covariant —
*mut T — Invariant —
UnsafeCell<T>, Cell<T>, RefCell<T>, Mutex<T> — Invariant —
fn(T) -> U — Contravariant Covariant
PhantomData<T> — Matches T —

Why &'a mut T Is Covariant in 'a but Invariant in T

If &'a mut T were covariant in T, you could coerce &mut &'static str into &mut &'short str, write a short-lived local string slice into it, and then read that dangling pointer back through the original 'static binding after the inner scope ended! The compiler prevents this by making &'a mut T invariant in T:

// ❌ does not compile
fn overwrite_with_short<'short>(slot: &mut &'short str, short_str: &'short str) {
    *slot = short_str;
}

fn main() {
    let mut static_slot: &'static str = "forever";
    {
        let local = String::from("temporary");
        // Rejected because `&mut T` is invariant in `T`:
        // `&mut &'static str` CANNOT coerce to `&mut &'short str`!
        overwrite_with_short(&mut static_slot, &local);
    }
    println!("{}", static_slot);
}
error[E0597]: `local` does not live long enough
  --> src/main.rs:12:48
   |
 7 |     let mut static_slot: &'static str = "forever";
   |                          ------------ type annotation requires that `local` is borrowed for `'static`
 8 |     {
 9 |         let local = String::from("temporary");
   |             ----- binding `local` declared here
...
12 |         overwrite_with_short(&mut static_slot, &local);
   |                                                ^^^^^^ borrowed value does not live long enough
13 |     }
   |     - `local` dropped here while still borrowed

Demonstrating Contravariance in Function Pointers (fn(T))

A function pointer fn(&'short str) that accepts any short-lived string reference can safely be called by code that only ever passes 'static strings (fn(&'static str)). Thus fn(&'short str) is a subtype of fn(&'static str):

fn accepts_any_str(s: &str) {
    println!("logged: {}", s);
}

fn run_with_static_callback(callback: fn(&'static str)) {
    callback("compile-time static string");
}

fn main() {
    // `accepts_any_str` has type `for<'a> fn(&'a str)`, which coerces via
    // contravariance to `fn(&'static str)`:
    let generic_fn: fn(&str) = accepts_any_str;
    run_with_static_callback(generic_fn);
}
logged: compile-time static string