Rust / Intermediate / Lesson 9
Lifetimes and borrowed APIs
Describe relationships between references without claiming to extend object lifetimes, and choose owned results when a borrow cannot safely escape.
On this page
Lesson goals
- Read named lifetime parameters as relationships, not runtime actions.
- Apply common lifetime elision rules and borrowed struct design.
- Distinguish a static reference from a static type bound.
A lifetime annotation does not keep data alive
References must remain valid for every use allowed by their type. Lifetime analysis relates those uses to the storage they borrow. A named lifetime parameter describes a relationship among input and output references; it does not allocate, copy, or extend the actual owner scope. The compiler can often infer local relationships, but public signatures sometimes need explicit information. When a diagnostic says a borrowed value does not live long enough, identify the owner and the last permitted use before editing annotations. A longer-looking lifetime name cannot make dead storage valid.
An output can depend on more than one input
The longer function may return either input, so its output is limited by a lifetime supported by both borrows. Using the same parameter does not require both owners to have identical full lifetimes; the borrows can be shortened to a suitable common region. The example keeps both owned strings alive through the result use. Length here is byte length, a deliberate policy that is simple for the ASCII inputs. If the function created a new combined String instead, returning an owned String would remove this particular output-borrow relationship.
fn longer<'a>(left: &'a str, right: &'a str) -> &'a str {
if left.len() >= right.len() { left } else { right }
}
fn main() {
let left = String::from("north");
let right = String::from("east");
println!("{}", longer(&left, &right));
}
north
north has more bytes, so its slice is returned. Both owners remain in scope throughout printing.
Borrowed fields become part of the type contract
A struct containing &str normally needs a lifetime parameter to describe its dependency on external text. The second program constructs an Excerpt that cannot outlive the String it borrows. Its method returns a view tied through ordinary method lifetime elision to the receiver borrow. Elision is a set of language rules, not a promise that arbitrary references are inferred however a caller wants. A function with one input reference often needs no explicit annotation; a method with &self commonly ties elided output references to that receiver. Understand the rule before adding named parameters everywhere.
struct Excerpt<'a> { text: &'a str }
impl<'a> Excerpt<'a> {
fn first_word(&self) -> &str { self.text.split_whitespace().next().unwrap_or("") }
}
fn main() {
let document = String::from("alpha beta");
let excerpt = Excerpt { text: &document };
println!("{}", excerpt.first_word());
}
alpha
The first word is a slice of document. No new owner is created, and document outlives the excerpt use.
Static describes a bound, not immortality
A string literal can provide an &'static str because its storage is available for the program lifetime. T: 'static instead means the type contains no borrowed references with shorter lifetimes; an owned String can satisfy that bound and still be dropped immediately. Do not replace a failing local lifetime with static unless the storage really supports it. Returning String, Vec, or another owned result is often the right repair for data created inside a function. Reference counting can support deliberate shared ownership, but it is not a universal substitute for a clear borrowed interface.
Review every path by which a reference escapes
An escaping reference may leave through a return value, a stored struct field, a closure, or a queued task. Identify all possible referents, not just the one selected by a test input. The lifetime contract must hold for every permitted branch. Prefer owned identifiers or copied summaries when retaining a full borrow unnecessarily couples unrelated scopes. Test borrowed APIs with different nested scopes using compile checks for rejected examples, and run only valid programs. Lifetime errors are compile-time evidence about an interface or owner relationship, not something to bypass with unsafe pointer conversion.
Common errors and fixes
Adding a lifetime name does not fix a local return.
- Why it happens
- The returned reference still points to dropped storage.
- How to fix it
- Return owned data or borrow an input owner.
- Prevent it next time
- Locate the referent before changing annotations.
A borrowed struct is retained beyond its text.
- Why it happens
- Its field dependency was ignored.
- How to fix it
- Shorten usage or store owned String fields.
- Prevent it next time
- Treat borrowed fields as public lifetime obligations.
A static bound is interpreted as a memory leak.
- Why it happens
- A type bound was confused with keeping a value forever.
- How to fix it
- Distinguish owned static types from static references.
- Prevent it next time
- Document storage and type bounds separately.
Check your understanding
1. Call longer with "ab" and "cd". State the result, tie policy, and which lifetime fact still matters even though both strings have equal length.
Show worked solution
The result is ab because the comparison uses >= and chooses left on a tie. Both argument borrows must support the lifetime required for using the returned slice, because the signature allows either source generally. A particular tie does not relax the public relationship. Change the contract only if the function is guaranteed always to return one specific input.
2. A function builds "draft" in a local String and wants to return it. Compare returning &str, String, and leaking an allocation.
Show worked solution
Return String to transfer ownership normally. Returning a slice of the local would escape dropped storage and is rejected. Intentionally leaking can create static storage in specialized designs, but permanently retaining an allocation is unnecessary for this ordinary result. The caller-owned String is valid independently and is cleaned up when the caller drops it.
Key takeaways
- Lifetimes relate uses to owners; they do not extend storage.
- Borrowed results must cover every possible source.
- Owned returns often simplify scope relationships.