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:
- Covariance: You can use a "longer" lifetime where a "shorter" one is expected. This is flexible and safe.
- Contravariance: You can use a "shorter" lifetime where a "longer" one is expected (rarely used, mostly in function signatures).
- 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.