Skip to content
← Back to explainers

Rust explainer

When elision fails: reading E0106

About 9 min to read. Allow extra time to work through examples.

E0106 means a reference in a signature or type needs a lifetime that Rust's elision rules cannot supply. This article covers the common forms of the error and shows how to choose an annotation by asking where the returned data comes from, instead of adding 'a until the code compiles.

This article uses Rust. You can make it your practice mode to browse related content across the site.

error[E0106]: missing lifetime specifier
 --> src/lib.rs:1:17
  |
1 | fn get_str() -> &str {
  |                 ^ expected named lifetime parameter

A lifetime is the region of a program during which a reference is valid. Function signatures and type definitions usually have to say how the lifetimes of their references relate. Rust lets you leave many of them out, and fills them in with a few fixed rules called lifetime elision. E0106 ("missing lifetime specifier") means you left a lifetime out somewhere the elision rules cannot fill it in.

A tempting response is to add <'a> to everything until the error goes away. That often compiles, but it can produce a signature that is stricter than it needs to be. A better approach is to answer one question first:

Where does the data behind the returned reference come from?

The answer determines the annotation. If the answer is "from a value the function creates and then drops", no annotation can help: the function has to return an owned value instead.

The elision rules in brief

For function and method signatures, the compiler applies these rules:

  1. Each elided lifetime in a parameter becomes its own separate lifetime parameter.
  2. If there is exactly one lifetime among the inputs (elided or named), it is used for every elided lifetime in the output.
  3. If there are several input lifetimes but one of them belongs to &self or &mut self, the lifetime of self is used for every elided output lifetime.

If an output reference still has no lifetime after these rules, the compiler reports E0106. Struct fields, enum variants and type aliases get no elision at all, so a reference there always needs a named lifetime.

Five common forms of E0106

1. No input lifetimes at all

fn get_str() -> &str {
    "hi"
}

rustc 1.98 reports:

error[E0106]: missing lifetime specifier
  = help: this function's return type contains a borrowed value, but there is
          no value for it to be borrowed from

No parameter is a reference, so rule 2 has nothing to copy. The compiler suggests two repairs, and these are the two real options:

  • Return an owned String. The caller receives the data and no lifetime is involved.
  • Return &'static str when the data really lives for the whole program: a string literal, or a reference to a const or static item. fn get_str() -> &'static str { "hi" } is correct because the literal "hi" is stored in the program for its whole run.

Adding a lifetime parameter, as in fn get_str<'a>() -> &'a str, makes the signature compile, but the caller chooses 'a, so the body must return data that is valid for any lifetime the caller picks. In practice that means 'static data again. Returning "hi" works; returning a reference to a String created inside the function does not compile (E0515, "cannot return reference to local variable"). The extra parameter adds nothing over writing 'static directly.

'static is a specific claim that the compiler checks. Use it when the data is genuinely static, not as a way to silence the error.

2. Two reference inputs and an elided output

fn frob(s: &str, t: &str) -> &str {
    s
}
error[E0106]: missing lifetime specifier
  = help: this function's return type contains a borrowed value, but the
          signature does not say whether it is borrowed from `s` or `t`
help: consider introducing a named lifetime parameter
  |
1 | fn frob<'a>(s: &'a str, t: &'a str) -> &'a str {

There are two input lifetimes and no self, so rules 2 and 3 do not apply. The help message states the question directly: is the result borrowed from s or from t?

The suggested fix gives both parameters the same lifetime. That always compiles when the body returns either parameter, which makes it a reasonable default suggestion, but it is often stricter than necessary. The section "A fix that compiles but is too strict" below shows the cost.

3. A struct or enum field holding a reference

struct Excerpt { part: &str }
//                     ^ error[E0106]

A type that stores a reference must declare a lifetime parameter for it:

struct Excerpt<'a> { part: &'a str }

Excerpt on its own is now not a complete type. Each use names a lifetime, as in Excerpt<'a>, or lets elision fill it in where elision is allowed. An Excerpt<'a> value cannot outlive the string it points into.

4. A type alias

type MyStr = &str;
//           ^ error[E0106]

A type alias has no parameters or receiver to take a lifetime from, so it needs its own parameter: type MyStr<'a> = &'a str;.

5. A path to a type that has a lifetime parameter

struct View<'a> { s: &'a str }

fn make() -> View {
//           ^^^^ error[E0106]
    View { s: "" }
}

View has a lifetime parameter even though the name View does not show it. Writing View in a signature is an elided lifetime, exactly like writing &str, and here there is no input to take it from. Here the compiler suggests View<'static>, which is correct only if every View this function returns points at static data.

When an input does exist, the hidden form compiles. For example, fn make(s: &str) -> View is accepted, and the output borrows from s. In rustc 1.98 it produces the warning "hiding a lifetime that's elided elsewhere is confusing" (lint mismatched_lifetime_syntaxes, on by default) and suggests writing View<'_>. The '_ lifetime means "elided, apply the normal rules", but makes it visible that View borrows something.

Consider these four signatures. Assume Config is an ordinary struct with no lifetime parameters.

fn one(config: &Config) -> &str
fn two(name: &str, sep: char) -> &str
fn three() -> &[u8]
fn four(a: &str, b: &str) -> String
Check your understanding For each signature above, say whether E0106 occurs and, if it does, how to fix it. Show answer

one: no error. There is one input lifetime, so by rule 2 the returned &str borrows from config. That fits a function returning, say, a name stored inside the configuration.

two: no error. char is not a reference and has no lifetime, so name provides the only input lifetime and rule 2 applies.

three: E0106, form 1. There is nothing to borrow from. If the bytes are a literal or come from a const or static, write -> &'static [u8]. If they are computed at run time, return Vec<u8>.

four: no error. The return type is owned, so it needs no lifetime. Returning an owned value removes the question entirely, which is worth considering before adding annotations.

A fix that compiles but is too strict

Back to form 2. The compiler's suggestion is:

fn frob<'a>(s: &'a str, t: &'a str) -> &'a str

It compiles. But suppose the body only ever returns a slice of s, and t is only read. With 'a on both parameters, the signature says the result may borrow from either input, so callers must keep both inputs alive for as long as they use the result. This ordinary caller is then rejected:

let haystack = String::from("hello world");
let found;
{
    let needle = String::from("wor");
    found = frob(&haystack, &needle);
}
println!("{found}");
// error[E0597]: `needle` does not live long enough

The caller creates a temporary needle, drops it at the end of the inner block, and keeps using found. The result really points into haystack, but callers are checked against the signature, not the body, so the compiler assumes it might point into needle.

Choose the signature from what the body returns:

// The result always borrows from `s`:
fn frob<'a>(s: &'a str, t: &str) -> &'a str

// The result may borrow from `s` or from `t`, depending on run-time values:
fn frob<'a>(s: &'a str, t: &'a str) -> &'a str

With the first signature the caller above compiles, because t gets its own independent elided lifetime. If any path in the body returns a slice of t, the first signature is rejected when the function body is checked (E0621, "explicit lifetime required in the type of t"), and the second one is required (or an equivalent with separate lifetimes for s and t, each declared to outlive the result's lifetime).

Both of these functions fail with E0106 as written:

// A
fn pick(a: &str, b: &str) -> &str {
    if a.len() >= b.len() { a } else { b }
}

// B
fn strip(text: &str, prefix: &str) -> &str {
    text.strip_prefix(prefix).unwrap_or(text)
}
Check your understanding Which signature does each body above need, and why can't the compiler choose it for you? Show answer

A needs fn pick<'a>(a: &'a str, b: &'a str) -> &'a str. Either branch can run, so the result may borrow from a or from b and must not outlive either of them. Tying both inputs to 'a is required here.

B needs fn strip<'a>(text: &'a str, prefix: &str) -> &'a str. Both strip_prefix(prefix) and unwrap_or(text) return slices of text; prefix is only read. Tying prefix to 'a would also compile, but it would force callers to keep their prefix alive for as long as they use the result, with no benefit.

Why doesn't the compiler infer this from the body? A function signature is the contract that callers are checked against; the compiler checks each call using only the signature. If lifetimes were inferred from the body, an edit inside the function could change its signature and break callers without any visible change to the API. Rust requires lifetimes in signatures to be stated or to follow the fixed elision rules for the same reason it requires parameter and return types to be written out.

The broader point: a signature can compile and still be stricter than necessary. The compiler checks that the body keeps the promises the signature makes. It cannot tell whether the signature makes promises you did not intend. For B, the tied form is safe but needlessly restrictive. Relaxing it later, by giving prefix its own lifetime, accepts every call that worked before, so that change is usually compatible. Going the other way, adding a constraint callers did not have before, can break existing callers.

A checklist

When you see E0106:

  1. Find the reference the error points at, and ask where the data behind it comes from.
  2. If it comes from exactly one parameter, give that parameter and the output the same named lifetime, such as 'a, and leave the other parameters with their own elided lifetimes.
  3. If it can come from more than one parameter, give each of those parameters and the output the same lifetime.
  4. If it comes from a local variable the function creates, no annotation will work. Return an owned value instead, or have the caller supply the storage (for example, by passing in a buffer or the source data as a parameter).
  5. If the function has no reference parameters, return an owned value, or return &'static if the data really is static.
  6. If the error is on a struct field, enum variant or type alias, add a lifetime parameter to that type or alias.

Steps 2 and 3 apply the same mechanical change to different answers from step 1. The real decision is made in step 1.

step 7 of 22

Ownership III: Lifetimes, Explicitly

Up next longest: writing your first 'a