error[E0106]: missing lifetime specifier
expected named lifetime parameter
This is the wall. Nearly everyone hits it in their first week of writing
functions that return references, and nearly everyone’s first instinct is to
sprinkle <'a> around until the red goes away.
Resist. E0106 is a question, and it has a right answer that depends on what you meant. Getting into the habit of answering it deliberately is the difference between understanding lifetimes and cargo-culting them for years.
The question is always the same one:
Where do the bytes behind the reference I am returning actually come from?
Answer that, and the annotation writes itself. If the honest answer is “from something I own and am about to drop”, then there is no annotation, and the error is telling you your design is wrong rather than your syntax.
The five shapes
1. No input lifetimes at all
fn get_str() -> &str {
// error[E0106]: missing lifetime specifier
// this function's return type contains a borrowed value,
// but there is no value for it to be borrowed from
"hi"
}
The compiler’s note is unusually direct: there is nothing to borrow from. No parameter carries a lifetime, so there is no candidate for the output.
This shape has no annotation-only fix. Writing fn get_str<'a>() -> &'a str
compiles the signature but makes an impossible promise — the caller picks 'a,
and the body must produce data valid for whatever they picked. Your two real
options are:
-
Return
String— own the data, hand it over, no lifetimes involved. -
Return
&'static str— if and only if the data genuinely is static: a string literal, aconst, astatic.fn get_str() -> &'static str { "hi" }is perfectly correct, because"hi"really does live for the whole program.
This is where learners first reach for 'static as a magic word. It is not
magic; it is a specific, checkable claim. Make it only when it is true.
2. Two inputs, elided output
fn frob(s: &str, t: &str) -> &str {
// error[E0106]
// help: this function's return type contains a borrowed value, but the
// signature does not say whether it is borrowed from `s` or `t`
}
Two lifetime-carrying parameters, so rule 2 cannot fire. The help: here spells
the question out for you. Answer it from the body.
Note what rustc then suggests:
help: consider introducing a named lifetime parameter
|
1 | fn frob<'a>(s: &'a str, t: &'a str) -> &'a str
It ties both parameters to 'a, because that is the only suggestion that is
always sound. It is frequently not the one you want. This shape has two
different correct answers, and choosing wrong is a real API bug rather than a
stylistic one. That is the next section.
3. A struct or enum field holding a reference
struct Excerpt { part: &str }
// ^ error[E0106]
A type that holds a reference must declare the lifetime it holds it for:
struct Excerpt<'a> { part: &'a str }
And now Excerpt is no longer a type — Excerpt<'a> is. That has consequences
which get their own article; they are the second-biggest stall point in the
topic.
4. A type alias
type MyStr = &str;
// ^ error[E0106]
Same rule, no receiver, no parameters, nothing to infer from. Write
type MyStr<'a> = &'a str;.
5. A path to a lifetime-carrying type
struct View<'a> { s: &'a str }
fn make() -> View {
// ^ error[E0106]
}
View is a path that carries a lifetime, even though nothing about the token
View says so. This “hidden” spelling is legal in some positions and confusing
in most; the lint that polices it gets its own article.
💡For each of these, say whether E0106 fires and, if it does, what the honest answer is. click to reveal
fn one(config: &Config) -> &str;
fn two(name: &str, sep: char) -> &str;
fn three() -> &[u8];
fn four(a: &str, b: &str) -> String;
one — no error. One lifetime-carrying parameter, so the output takes its lifetime. The returned &str borrows from the config, which is exactly what you would want.
two — no error. char carries no lifetime, so name is the only carrier. Elision fires.
three — E0106, shape 1. Nothing to borrow from. If the bytes are a literal or a const, say -> &'static [u8]. If they are computed, return Vec<u8>.
four — no error, and no lifetime anywhere. The return type is owned. This is the quiet reminder that the whole problem disappears when you stop returning references — which is sometimes exactly the right call, and is always worth considering before you start annotating.
The “fix” that compiles and is still wrong
Back to shape 2. Here is the fix nearly everyone applies:
fn frob<'a>(s: &'a str, t: &'a str) -> &'a str { ... }
It compiles. Tests pass. And if the returned slice actually comes only from s,
it is a design bug that will annoy every caller you have.
Remember what 'a on both parameters means: the result is valid only over a
region where both inputs are valid. So this ordinary caller stops compiling:
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 built a scratch value, used it, dropped it, and wanted to keep the
result — which points into haystack, not into needle. Your signature said
otherwise, so the compiler believes your signature.
This is the number-one lifetime design smell in real crates. The correct answers are:
// the result comes from `s`:
fn frob<'a>(s: &'a str, t: &str) -> &'a str
// the result comes from either, decided at runtime:
fn frob<'a>(s: &'a str, t: &'a str) -> &'a str
Both are legitimate. The body decides which. If every return in the body
produces a slice of s, the first is right and the second over-constrains. If
some paths return a slice of t, the second is the only option.
💡Which signature does each of these bodies want, and why can't the compiler work it out for you? click to reveal
// 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)
}
A wants <'a>(a: &'a str, b: &'a str) -> &'a str. Both branches can return, so the result must be valid wherever both are — the constraint is real, not gratuitous.
B wants <'a>(text: &'a str, prefix: &str) -> &'a str. Every path returns a slice of text; prefix is only ever read. Tying prefix to 'a would force callers to keep their prefix alive for as long as they hold the result, for no reason at all.
Why can’t the compiler infer it? Because a signature is a contract with callers, and the compiler type-checks call sites against the signature alone. If it derived lifetimes from the body, then changing the body could silently break every caller — the classic argument for why type signatures are written, not inferred, at API boundaries. Rust makes the same trade for lifetimes.
There is a deeper point hiding here, and it is worth naming: a signature can compile and still be wrong. The compiler verifies soundness, never intent. Nothing in A‘s over-tied form is unsound; it is merely a promise you did not need to make and cannot take back without a breaking change.
The checklist
When you see E0106:
- Find the returned reference. Ask where its bytes come from.
-
If they come from exactly one parameter — give that parameter
'a, give the output'a, and leave every other parameter alone. -
If they can come from more than one — give
'ato each parameter that can be the source, and to the output. - If they come from a local — stop. No annotation exists. Change the return type, or change who owns the storage.
-
If there are no parameters at all — return an owned value, or
&'staticif the data really is static.
Note that steps 2 and 3 are the same mechanical move applied to a different answer to step 1. All the thinking is in step 1.