Skip to content

Chapter 4 of 8

Lifetimes, Variance, and PhantomData

Lifetime annotations are the mechanism by which the borrow checker understands how multiple references relate in time. Written as generic parameters like 'a, they appear in function signatures, struct definitions, and trait bounds. A reference such as &'a i32 is one that lives at least as long as the lifetime 'a. The compiler applies a set of elision rules so that explicit annotations are usually unnecessary: each input reference gets its own lifetime parameter, and if a function has exactly one input reference, that lifetime is automatically assigned to all output references. The special lifetime 'static describes values that live for the entire duration of the program, and string literals such as "hello" have type &'static str.

Lifetime parameters on structs, written as struct Ref<'a> { r: &'a i32 }, express that the struct cannot outlive the data it borrows. A lifetime bound like T: 'a on a generic says that every reference inside T must outlive 'a. When one lifetime is known to live longer than another, written 'a: 'b, references with 'a can be substituted wherever references with 'b are expected — this relationship is called lifetime subtyping. A higher-ranked trait bound, written with the for<'a> syntax, expresses that a condition must hold for every possible lifetime, and is especially useful when describing closures and function pointers.

Variance describes how substitutions in a type position propagate. &T is covariant in both T and its lifetime — substituting a longer-lived reference for a shorter-lived one is always safe. &mut T, however, is invariant in T because write access would otherwise allow type confusion; raw pointers split similarly, with *const T covariant and *mut T invariant. The zero-sized marker PhantomData<T> is a common tool for influencing variance, drop-check behavior, and auto-trait inference in smart-pointer-like types — it tells the compiler to treat a struct as if it owned or borrowed a T even when T is not stored directly. The drop check itself is the compile-time analysis that prevents generic types from outliving values they may need to drop during teardown; PhantomData participates in this analysis, and the unsafe attribute #[may_dangle] on a Drop impl can be used to declare that the drop implementation does not access data of the type's generic parameters, relaxing the check when implementing low-level structures like intrusive linked lists.

All chapters
  1. 1Ownership Rules and Memory
  2. 2Moves, Copy, and Clone
  3. 3Borrowing and the Borrow Checker
  4. 4Lifetimes, Variance, and PhantomData
  5. 5Slices, Strings, and Deref Coercion
  6. 6Interior Mutability and Runtime Enforcement
  7. 7Shared Ownership Across Threads
  8. 8Patterns, Iteration, and Unsized Types

Drill it

Reading is not remembering. These come from the Rust Ownership And Borrowing Explained deck:

Q

What are the three ownership rules in Rust?

Each value in Rust has an owner. There can only be one owner at a time. When the owner goes out of scope, the value is dropped.

Q

What happens to a value when its owner goes out of scope in Rust?

It is dropped — its drop function runs and its memory is freed.

Q

Which standard library function is called when a value goes out of scope?

drop, from the Drop trait, is called automatically at the end of the variable's scope.

Q

What is a "move" in Rust?

A transfer of ownership: assigning a value to another variable or passing it to a function moves the value, invalidating the source binding.