← Debugging Guides

阅读中文版本

Rust — Lifetime Errors from Multiple Anonymous References

Demystify lifetime errors like explicit lifetime required with two reference parameters, apply elision rules deliberately, and annotate the data flow you intend.

advanceddebuggingrustlifetimesborrow-checker

Symptoms

  • error[E0106] missing lifetime specifier or explicit lifetime required, considered using anonymous lifetime errors
  • The compiler says it cannot infer an appropriate lifetime with two & references in the signature
  • The fix seems arbitrary until the elision rules are known, then becomes mechanical

Possible causes

  • A function with two or more reference parameters returns a reference, and no elision rule picks the output lifetime
  • A struct holding references needs a lifetime parameter that method signatures must thread through
  • Mixing &self with other references in a returned reference leaves elision ambiguous
  • Closures and iterators add hidden lifetime bounds that surface as confusing notes

Diagnosis steps

Read which lifetime the compiler wants

cargo check 2>&1 | grep -B2 -A10 "E0106\|explicit lifetime"

Interpretation

The message names the return type and the candidate references — it is asking you to declare which input the output borrows from.

Apply the elision rules mentally

cargo check 2>&1 | head -30

Interpretation

Rule set: each elided param gets its own lifetime; if one input is &self it wins; otherwise multiple candidates with a returned reference means you must annotate.

Annotate the intended flow

cargo check

Interpretation

Writing the signature that matches the real data flow turns the error into documentation: fn longest(a: &str, b: &str) -> &str is invalid; the annotated version states the contract.

Root cause

Rust lifetime elision fills in lifetimes automatically only in unambiguous shapes: single reference parameters, or methods where &self is the obvious owner of the result. With two or more candidate references feeding a returned reference, the compiler cannot know which one the result borrows from, so it demands an explicit annotation stating the contract.

Fix

Write the explicit lifetime that matches the real data flow — which parameter the return value borrows from. Use two different lifetimes when they truly differ, borrow from &self in methods via elision, and prefer owned returns (String, Vec) when no borrow relationship exists.

Risk note

Copying a single lifetime annotation everywhere to satisfy the compiler can over-constrain APIs and block valid callers. Annotate the actual flow, not the first thing that compiles.

Verify the fix

The annotated signature compiles, and callers exercising both intended relationships (shorter and longer argument lifetimes) type-check as designed.

Symptom walkthrough

The compiler asks a question rather than reports a bug:

error[E0106]: missing lifetime specifier
 --> src/lib.rs:3:33
  |
3 | fn longest(a: &str, b: &str) -> &str {
  |                                 ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value with
    an unknown lifetime

The elision rules explain why only this shape fails: one reference parameter — the output gets that lifetime automatically; &self plus others — self wins; two unrelated references with a returned reference — no rule applies, and the compiler asks you to say which input the output borrows from. It is asking for the contract, not complaining about your logic.

Fix in practice

State the flow you intend:

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

One shared lifetime here says the result may borrow from either input and lives as long as both do. When the flows differ, split them:

fn wrap<'a, 'b>(outer: &'a str, inner: &'b str) -> String {
    // owned return: no borrow relationship, no annotation pressure
    format!("{}{}", outer, inner)
}

Structs holding references thread the parameter through impls:

struct Parser<'src> { source: &'src str }

impl<'src> Parser<'src> {
    fn token(&self) -> &'src str { /* elided &self: correct by rule 2 */ }
}

And the design escape: when no genuine borrow relationship exists, return owned data and delete the problem.