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

Covariance in Collection Types

Explain why some collection structures cannot be covariant over their inner types.

In this lesson, you will learn why Rust enforces strict invariance in collection types, preventing them from automatically "narrowing" their type parameters even when a subtyping relationship seems obvious.

The Concept of Invariance

In Rust, variance describes how subtyping in one type carries over to another. We say a type is covariant if List<T> is a subtype of List<U> whenever T is a subtype of U. While immutable references like &'a T are covariant over 'a, collections like Vec<T> or Box<T> are invariant.

This means that even if a type Dog is a subtype of Animal, a Vec<Dog> is not considered a Vec<Animal>.

Why Collections Can’t Be Covariant

The primary reason is mutation. If Rust allowed you to treat a Vec<Dog> as a Vec<Animal>, you could inadvertently insert a Cat into that vector:

fn add_animal(animals: &mut Vec<Animal>) {
    animals.push(Cat); // Perfectly valid if 'animals' is Vec<Animal>
}

let mut dogs: Vec<Dog> = vec![Dog];
// If Rust allowed this cast:
add_animal(&mut dogs); 
// We just put a Cat into a Vec<Dog>! Crash/Undefined Behavior!

Because Vec<T> owns its data and allows both reading and writing, treating it as a collection of a broader type would break the type safety of the underlying data. The compiler must ensure that the type you put in is exactly the type the container expects.

Invariance and Lifetimes

This logic extends to lifetimes. If you have a Cell<&'static str>, you cannot treat it as a Cell<&'a str> (where 'a is a shorter lifetime). If you could, you might store a short-lived reference into a container that promises to hold onto that reference for the program's entire duration. Because Cell allows interior mutability, the compiler forces the lifetime to be exact—it is invariant.

When you build your own data structures, remember that if you provide a &mut reference to the inner elements or allow interior mutability, your type must be invariant over its generic parameters to remain memory safe.

Key Takeaway: Collections are invariant because allowing them to be covariant would enable storing incompatible types via mutable access, violating memory safety.