When a lifetime error appears, start with three questions: what value is borrowed, who owns it, and where is the reference needed? Then read the highlighted locations and any notes about the required lifetime relationship.
An annotation describes a relationship between references. It does not keep
an owner alive or make overlapping accesses valid. The examples below show
when to change a signature and when to change storage or control flow.
Diagnostic codes were checked with Rust 1.98.1, edition 2024; exact wording
can differ between compiler versions. Failing function snippets can be checked
as a library with rustc --edition 2024 --crate-type lib example.rs.
E0106: a missing lifetime
Rust can infer many reference lifetimes from a function's signature, using lifetime elision rules. Those rules cannot choose a source for these outputs:
fn get_str() -> &str { "hi" }
fn first(s: &str, _t: &str) -> &str { s }
The first function has no input reference. The second has two, even though
its body returns only s. Elision does not inspect the body to choose one.
Make the intended sources explicit:
fn get_str() -> &'static str { "hi" }
fn first<'a>(s: &'a str, _t: &str) -> &'a str { s }
A string literal provides static storage. In first, the output is tied to
s; it does not need the other input's lifetime. References stored in a
struct or ordinary type alias also need a lifetime, for example
struct Excerpt<'a> { part: &'a str } and type MyStr<'a> = &'a str.
E0515: returning a reference into local storage
fn message<'a>() -> &'a str {
let text = String::from("hi");
text.as_str()
}
text owns the bytes and is dropped when the function returns. Naming 'a
cannot make those bytes survive. Return an owned String, or borrow storage
supplied by the caller. Here an owned return is straightforward:
fn message() -> String {
String::from("hi")
}
E0597: the owner does not live long enough
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("{r}");
}
The reference is needed after x leaves scope. Either use r inside that
scope, move x into a scope that includes the use, or keep an owned value
instead. For this integer, copying its value would also work.
E0716: a borrowed temporary is dropped too soon
fn identity(s: &str) -> &str { s }
fn text() -> String { String::from("hi") }
fn main() {
let r = identity(&text());
println!("{r}");
}
The temporary String is dropped at the end of the let r statement, while
the next statement still needs its bytes. Give it a binding in a scope that
includes the use:
let owned = text();
let r = identity(&owned);
println!("{r}");
let r = &text(); also works here because that initializer gets temporary
lifetime extension. Passing the borrow through identity does not receive
the same extension. Temporary lifetimes depend on the expression's syntax
and context, so naming the owner often makes the intended scope clearer.
E0597 and E0716 both concern storage that would disappear before a reference finishes being used. The distinction in these examples is a named local versus a temporary. In either case, check the owner's scope and the borrow's last use before adding annotations.
E0621: the signature omits a needed relationship
fn choose<'a>(_x: &'a str, y: &str) -> &'a str {
y
}
The output promises 'a, but y has a separate elided lifetime. If the
function should return y, declare y: &'a str. If it should return x,
fix the body instead. Choose according to the function's contract rather
than applying a suggestion without checking its effect on callers.
E0505: moving an owner while it is borrowed
fn main() {
let s = String::from("hi");
let r = &s;
let t = s;
println!("{r} {t}");
}
The final print needs r, so its borrow is still needed when s moves.
Finish using r before moving s, or clone the string if you need two owned
values. Another option is to keep borrowing s instead of moving it.
E0499 and E0502: overlapping borrows
This example needs two mutable borrows of the same vector at once:
fn main() {
let mut v = vec![1];
let a = &mut v;
let b = &mut v;
a.push(1);
b.push(2);
}
It produces E0499. Move a.push(1) before creating b so the first borrow
is no longer needed when the second begins.
This example keeps a shared reference to an element across a vector mutation:
fn main() {
let mut v = vec![1];
let r = &v[0];
v.push(2);
println!("{r}");
}
It produces E0502. Print before pushing, or copy the integer into r with
let r = v[0]. The copy no longer borrows the vector.
These local examples need changes to the accesses. In larger programs, an unnecessarily restrictive function signature can also make a borrow last too long. Review the signature if the reported conflict involves a call; do not assume every borrow conflict can be fixed by moving a local statement.
Some lifetime diagnostics have no error code
A diagnostic can say lifetime may not live long enough without an E code.
You can still use its highlighted locations and lifetime relationships:
fn longest<'a, 'b>(a: &'a str, b: &'b str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
The second branch returns 'b, but the output requires 'a. A bound
'b: 'a would require 'b to outlive 'a. Another suitable signature uses
one lifetime for both inputs and the output. That lets the caller borrow
both inputs for a common region in which the result can be used.
Closure inference can produce an uncoded diagnostic too:
fn main() {
let identity = |x: &i32| x;
let _ = identity;
}
A function fn identity(x: &i32) -> &i32 { x } has an input-output lifetime
relationship through elision. A closure may need expected-type context to
establish that relationship. For this non-capturing closure, an explicit
function-pointer type works:
fn main() {
let identity: fn(&i32) -> &i32 = |x| x;
let n = 7;
assert_eq!(*identity(&n), 7);
}
Check suggestions against the intended API
T: 'static means a type has no borrowed references that are too short-lived
for that bound. It does not mean a value of T must remain alive forever:
an owned String satisfies the bound and can still be dropped normally.
By contrast, &'static str is a reference valid for the static lifetime.
A suggested 'static bound may be appropriate for your API, but it can also
reject borrowed inputs you meant to support. Check whether the function
should own its data, borrow from a caller, or return a result tied to an input.
Older explanations may show retired codes. For example, the documentation for E0759 and E0477 says those codes are no longer emitted. Use older examples to understand the ownership relationship, and compare them with your current diagnostic rather than relying on an exact text match.