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

Contravariance in Function Types

Recognize how function signatures restrict the substitution of types based on lifetime variance.

In this lesson, you will learn why function arguments are "contravariant," allowing you to substitute a function that accepts a broader set of inputs for one that expects a narrower set.

Understanding the Direction of Variance

We have previously discussed how lifetimes behave when they are part of a structure or a collection (covariance). However, function arguments follow the exact opposite rule. This is called contravariance.

Think of a function signature as a contract. If a piece of code expects a function that takes a &'a str, it is promising to provide a reference that lives for at least 'a. If you give it a function that can handle any lifetime (an 'static reference, for example), that is perfectly safe. Because the function is capable of handling "more" than what is required, it is a valid substitute.

A Concrete Example

Consider this scenario where we have two functions with different lifetime requirements:

fn needs_long_lived(s: &'static str) { println!("{}", s); }
fn needs_short_lived(s: &'a str) { println!("{}", s); }

If you have a variable that requires a function signature like fn(&'a str), you can safely pass a function that expects fn(&'static str). Why? Because &'static str is a subtype of &'a str (it lives at least as long). Even though the lifetime itself is covariant, the function input position flips this requirement.

If a function promises to work with any &'a str, it will naturally work with the more specific &'static str. Conversely, you cannot replace a function expecting a &'static str with one that only handles &'a str, because the latter might not be able to handle a truly static string if it needs to perform operations restricted by the shorter lifetime.

Why this matters

In Rust, the compiler uses these rules to determine if you can pass a function pointer or a closure into a higher-order function. Because arguments are contravariant, Rust allows you to be more flexible with the lifetimes you provide to function signatures, ensuring the program remains memory-safe while avoiding unnecessary trait bound errors.

Key Takeaway: Function arguments are contravariant, meaning a function that accepts a broad (longer) lifetime can safely be used in place of a function that requires a specific (shorter) lifetime.