| name | rust-lifetimes |
| description | Understand and work with Rust lifetimes, lifetime annotations, and lifetime elision. Use when fixing lifetime errors, understanding borrowing rules, working with references, or implementing traits with lifetime parameters. Handles explicit lifetimes, lifetime elision, higher-ranked trait bounds, and common lifetime patterns. |
Rust Lifetimes
Guidelines for understanding and working with Rust lifetimes and lifetime annotations.
When to Use This Skill
- Fixing lifetime errors
- Understanding borrowing rules
- Working with references
- Implementing traits with lifetime parameters
- Understanding when explicit lifetimes are needed
- Working with higher-ranked trait bounds (HRTB)
Basic Lifetime Syntax
Lifetime Parameters
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
Structs with Lifetimes
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn new(part: &'a str) -> Self {
Self { part }
}
}
Multiple Lifetimes
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str
where
'b: 'a,
{
x
}
Lifetime Elision Rules
Three Elision Rules
- Each parameter gets its own lifetime
- If there's exactly one input lifetime, it's assigned to all output lifetimes
- If there's
&self or &mut self, its lifetime is assigned to all output lifetimes
Examples
fn first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x
}
fn identity<'a>(x: &'a str) -> &'a str {
x
}
impl<'a> ImportantExcerpt<'a> {
fn get_part(&self) -> &str {
self.part
}
}
Common Lifetime Patterns
Returning References
fn get_first_word<'a>(s: &'a str) -> &'a str {
s.split_whitespace().next().unwrap_or("")
}
fn bad_example(s: &str) -> &str {
let temp = String::from("temp");
&temp
}
Structs Holding References
struct Parser<'a> {
input: &'a str,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Self { input }
}
fn parse(&self) -> &'a str {
self.input
}
}
Lifetimes in Methods
impl<'a> ImportantExcerpt<'a> {
fn get_part(&self) -> &'a str {
self.part
}
fn compare<'b>(&self, other: &'b str) -> &'b str {
other
}
fn combine<'b>(&self, other: &'b str) -> &'a str
where
'b: 'a,
{
self.part
}
}
Static Lifetime
'static Lifetime
let s: &'static str = "I have a static lifetime.";
fn get_static() -> &'static str {
"static string"
}
When to Use 'static
const CONSTANT: &'static str = "constant";
fn bad_function() -> &'static str {
}
Higher-Ranked Trait Bounds (HRTB)
For Closures
fn call_with_ref<F>(f: F)
where
F: for<'a> Fn(&'a i32),
{
let value = 42;
f(&value);
}
For Trait Objects
trait Trait {
fn method(&self) -> &str;
}
fn use_trait<T: for<'a> Trait>(t: T) {
let s = t.method();
}
Common Lifetime Errors
Error: Missing Lifetime Parameter
fn longest(x: &str, y: &str) -> &str {
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
Error: Borrowed Value Does Not Live Long Enough
fn example() -> &str {
let s = String::from("hello");
&s
}
fn example() -> String {
let s = String::from("hello");
s
}
Error: Lifetime Mismatch
struct Holder<'a> {
value: &'a str,
}
fn example<'a, 'b>(h1: &'a Holder<'a>, h2: &'b Holder<'b>) -> &'a str {
h2.value
}
fn example<'a>(h1: &'a Holder<'a>, h2: &'a Holder<'a>) -> &'a str {
h2.value
}
Important Rules
- Lifetimes ensure references are valid: They don't change how long values live
- Use elision when possible: Let Rust infer lifetimes when rules apply
- Be explicit when needed: Add lifetimes when elision doesn't work
- Document lifetime relationships: Use where clauses to clarify relationships
- Avoid 'static unless necessary: Don't force 'static when not needed
- Understand the error: Lifetime errors tell you what's wrong
Lifetime Patterns
Returning Iterator
fn words<'a>(s: &'a str) -> impl Iterator<Item = &'a str> {
s.split_whitespace()
}
Generic Lifetimes
fn process<'a, T>(value: &'a T) -> &'a T
where
T: 'a,
{
value
}
Lifetime in Traits
trait Processor<'a> {
type Output: 'a;
fn process(&self, input: &'a str) -> Self::Output;
}
Examples from Project
Look for lifetime usage in:
- Parsers that hold references to input
- Iterators over slices
- Structs that borrow data
- Trait implementations with references
Common Solutions
Problem: Need to Return Reference
fn get_first<'a>(s: &'a str) -> &'a str {
s.split_whitespace().next().unwrap_or("")
}
Problem: Struct Needs to Hold Reference
struct Holder<'a> {
value: &'a str,
}
Problem: Multiple References with Different Lifetimes
fn combine<'a, 'b>(x: &'a str, y: &'b str) -> &'a str
where
'b: 'a,
{
x
}