Course contentsShow
← Rust
Lesson 358 of 1,9104. Lifetimes and Advanced OwnershipFree lesson

Lifetime Variances

Analyze how covariance and contravariance affect the flexibility of type parameters.

In this lesson, you will learn how lifetime variance determines whether a generic type can safely accept a "narrower" or "broader" lifetime than the one it was originally defined with.

The Problem of Flexibility

When you have a generic type like &'a T or Vec<T>, the compiler must decide: if you have a T with a long lifetime, can you use it where a shorter lifetime is expected? This "subtyping" relationship is what we call variance.

There are three types of variance in Rust:

  1. Covariance: You can use a "longer" lifetime where a "shorter" one is expected. This is flexible and safe.
  2. Contravariance: You can use a "shorter" lifetime where a "longer" one is expected (rarely used, mostly in function signatures).
  3. Invariance: The lifetimes must match exactly. This is rigid.

Covariance: The Common Case

Most read-only types are covariant. Consider a reference: &'a T. Because a reference is just a read-only window into data, if you have a reference that lives for a long time, it is perfectly safe to treat it as a reference that lives for a shorter time.

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

let long_lived = "I live forever"; // &'static str
let short_lived: &str = long_lived; // Works! Covariance allows this.

Invariance: The Mutable Problem

Things get rigid when we add interior mutability or mutable references. If you have &'a mut T, it is invariant. Why? Because if you could treat a &'a mut &'static str as a &'a mut &'b str, you could accidentally write a short-lived string into a variable that expects a long-lived one, breaking memory safety.

Because &mut T allows you to replace the value inside, the lifetime of the type inside must be exact. If it were covariant, you could trick the compiler into thinking a short-lived reference is a long-lived one by overwriting the contents of the mutable container.

Variance is the hidden mechanism that allows Rust to be as flexible as possible with read-only data while staying strictly rigid when you have the power to change that data.

Key Takeaway: Covariance allows you to substitute long-lived references for short-lived ones safely, while invariance prevents mutable types from undermining that same safety.