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:
- Covariant over
T: Preserves the subtyping direction (F<Sub> <: F<Super>). You can passF<&'long str>whereF<&'short str>is expected. - Invariant over
T: No subtyping relationship exists in either direction.F<&'long str>andF<&'short str>cannot be substituted for one another. - Contravariant over
T: Reverses the subtyping direction (F<Super> <: F<Sub>). You can passF<&'short str>whereF<&'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