Advanced Lifetimes and Borrowing

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: 'short means 'long is a subtype of 'short, and 'static is 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).