Course contentsShow
← Rust
Lesson 250 of 1,9103. Borrowing and ReferencesFree lesson

Variance in Generics

Explain covariance and contravariance in the context of lifetime subtyping.

In this lesson, you will learn how Rust determines if a generic type can be safely swapped for another based on the lifetimes they contain.

The Problem of "Fitting In"

You already know that if you have a lifetime 'long that outlives 'short, then any reference valid for 'long can be used where a reference for 'short is required. This is lifetime subtyping.

But what happens when we put those references inside a generic wrapper, like &'a T? Variance is the set of rules that tells the compiler: "If T is a subtype of U, is Wrapper<T> a subtype of Wrapper<U>?"

Covariance

Most types in Rust are covariant. This means the "subtyping" relationship is preserved. For example, a reference &'a T is covariant over 'a. If 'long : 'short (meaning 'long is at least as long as 'short), then &'long T is a subtype of &'short T.

Think of this as "widening": you can safely treat a long-lived reference as a shorter-lived one because the data will definitely still be there.

Contravariance

Contravariance is the opposite: the relationship is flipped. This is rare in Rust, but it occurs in function types regarding their arguments.

If you have a function that expects a long-lived reference, it is actually more flexible than a function that expects a specific, short-lived reference. A function that can process anything that lives for 'short is "smaller" in scope than a function that requires a strict 'long lifetime.

Why Does This Matter?

Consider this snippet:

fn takes_short(s: &'short str) {}
let long_str: &'long str = "hello";

// This works because &T is covariant over its lifetime!
takes_short(long_str);

If the compiler didn't understand that &'long str fits into &'short str, you would have to manually cast your lifetimes every single time you passed a variable into a function. Variance handles this plumbing for you automatically, ensuring that your references are always "safe to shorten" but never "unsafe to lengthen."

Key Takeaway: Covariance allows a generic type to be used where a "smaller" or "shorter" version of its lifetime is expected, making Rust code flexible and intuitive.