Lifetime Subtyping
Lifetime subtyping is the relationship between two lifetimes in which a longer lifetime 'long is a subtype of a shorter lifetime 'short—written as the outlives bound 'long: 'short. Although Rust has no nominal struct inheritance, it uses subtyping on lifetimes so a reference valid for a larger region of code can be substituted wherever a reference valid for a smaller nested region is expected.
Why 'long Is a Subtype of 'short
In type theory, Sub is a subtype of Super (Sub <: Super) if any value of type Sub can safely be used where a value of type Super is required.
- If code requires a reference that stays valid for at least the inner block
'short, passing a reference that stays valid for the outer block'long(or'static) satisfies every requirement of'short. - Therefore,
'long: 'shortmeans'longis a subtype of'short, and'staticis a subtype of every lifetime'a('static: 'a).
Outer Scope ('long)
| let outer_text = String::from("lives for 'long");
| let r_long: &'long str = &outer_text;
|
| Inner Scope ('short, where 'long: 'short)
| +---------------------------------------------------------------+
| | // Because `'long: 'short`, `&'long str` coerces to |
| | // `&'short str` inside the inner scope: |
| | let r_short: &'short str = r_long; |
| +---------------------------------------------------------------+
v
Explicit Outlives Syntax ('b: 'a and T: 'a)
When two distinct lifetime parameters must be kept separate rather than unified into a single common lifetime—for example, when a parser borrows a long-lived source buffer 'src while being mutated through a short-lived borrow 'ctx—the bound 'src: 'ctx expresses that 'src outlives 'ctx:
| Bound Syntax | Meaning |
|---|---|
'long: 'short |
Lifetime 'long covers all of lifetime 'short ('long is a subtype of 'short) |
'a: 'b and 'b: 'a |
'a and 'b are the exact same lifetime |
T: 'a |
Any references contained inside type T outlive 'a |
T: 'static |
T contains no non-'static references (includes all owned types like String, Vec<u8>, i32, as well as &'static str) |
Separating Context Lifetimes from Source Lifetimes
Without lifetime subtyping, tying a temporary context's mutable borrow &'a mut Context<'a> to the same lifetime 'a as the underlying source string locks the struct for the entire 'a lifetime. Using two lifetimes 'src and 'ctx with 'src: 'ctx allows returning a slice tied to 'src while releasing the mutable borrow 'ctx immediately:
struct Scanner<'src> {
source: &'src str,
pos: usize,
}
struct ScannerHandle<'ctx, 'src: 'ctx> {
scanner: &'ctx mut Scanner<'src>,
}
impl<'ctx, 'src: 'ctx> ScannerHandle<'ctx, 'src> {
// Returns a slice with lifetime `'src` (outliving the temporary `'ctx` handle!):
fn next_word(&mut self) -> Option<&'src str> {
let rest = &self.scanner.source[self.scanner.pos..];
let trimmed = rest.trim_start();
if trimmed.is_empty() {
return None;
}
let skipped = rest.len() - trimmed.len();
let word_len = trimmed.find(' ').unwrap_or(trimmed.len());
let start = self.scanner.pos + skipped;
self.scanner.pos = start + word_len;
Some(&self.scanner.source[start..self.scanner.pos])
}
}
fn main() {
let text = String::from("alpha beta gamma");
let mut scanner = Scanner {
source: &text,
pos: 0,
};
let first_word: &str = {
let mut handle = ScannerHandle {
scanner: &mut scanner,
};
handle.next_word().unwrap()
}; // `handle` ('ctx) drops here, unlocking `scanner`, while `first_word` ('src) stays valid!
println!("first_word: {:?}, scanner.pos: {}", first_word, scanner.pos);
}
first_word: "alpha", scanner.pos: 5
Implicit Outlives Inference on Struct Definitions
When a struct stores &'a T or &'a mut T, the compiler automatically infers the bound T: 'a (since a reference living for 'a cannot point to a type T that itself contains shorter-lived references).