Course contentsShow
← Rust
Lesson 303 of 1,9103. Borrowing and ReferencesFree lesson

Lifetime Elision with Lifetime Parameters

Balance implicit and explicit lifetime annotations in complex function chains.

In this lesson, you will learn how to determine when Rust’s compiler can automatically infer lifetimes and when you must step in with explicit annotations to resolve complex ambiguity.

The Invisible Rules

Rust’s lifetime elision rules are a set of heuristics that allow you to omit explicit lifetime annotations in common scenarios. The compiler applies three specific rules to function signatures:

  1. Each parameter that is a reference gets its own lifetime parameter.
  2. If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.
  3. If there are multiple input lifetime parameters, but one of them is &self or &mut self, the lifetime of self is assigned to all output lifetime parameters.

When Elision Breaks Down

Elision works perfectly for simple functions, but it hits a wall when you chain functions together or pass references between multiple containers. Consider this example:

fn select_string(a: &str, b: &str) -> &str {
    if a.len() > 5 { a } else { b }
}

This code fails to compile. The compiler sees two input references, so it gives them two distinct lifetimes (Rule 1). However, since there isn't a single input lifetime or a self parameter, Rule 2 and 3 do not apply. The compiler doesn't know if the returned reference belongs to a or b, so it refuses to guess.

Balancing Explicit Annotations

When the compiler cannot infer the relationship, you must provide a "bridge" using explicit parameters. By adding <'a>, you tell the compiler that both inputs and the output are constrained by the same minimum scope:

fn select_string<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() > 5 { a } else { b }
}

In complex chains, you only need to annotate the boundaries where the compiler loses track of the relationship. If a function takes a reference and returns a part of that same reference, elision usually handles it. If you are comparing or merging two separate borrowed sources, you must explicitly link their lifetimes to satisfy the borrow checker's demand for safety.

Key Takeaway: Use lifetime elision to keep your code clean, but manually introduce lifetime parameters the moment your function involves multiple input references that must share a common valid scope.