Mir luqman
4
文章
3353
查看
0
关注者
Lifetimes in Rust Complete Beginner Guide
What is a Lifetime?
A lifetime is Rust's way of tracking how long references are valid.
Simple analogy:
Imagine you borrow a book from a library. The book has a lifetime that represents how long it exists. Your borrow also has a lifetime, which is how long you have it. You cannot keep the borrow longer than the book exists.
In Rust, the same principle applies:
Variables have lifetimes, spanning from when they are created to when they are destroyed.
References have lifetimes that determine how long they can safely point to data.
Rust ensures references never outlive the data they point to.
The Problem: Dangling References
What Rust prevents:
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("{}", r); // ERROR: x is gone, r points to nothing
}
Error:
error[E0597]: x does not live long enough
Why?
The variable x is destroyed at the end of the inner scope.
The reference r still tries to use x after it has been destroyed.
This would be a dangling reference, pointing to invalid memory.
Rust prevents this at compile time.
The Solution: Lifetimes
Rust uses lifetime annotations to track how long references are valid.
Syntax:
&i32 // a reference
&'a i32 // a reference with an explicit lifetime 'a
&'a mut i32 // a mutable reference with an explicit lifetime 'a
Part | Meaning |
|---|---|
& | Reference |
'a | Lifetime parameter (read as "tick a") |
i32 | Type |
The notation 'a is like a label saying "this reference is valid for lifetime 'a".
Example 1: Function with Lifetimes
Problem without lifetimes:
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
Error:
error[E0106]: missing lifetime specifier
Why?
Rust does not know if the returned reference is from x or y.
It cannot determine how long the returned reference is valid.
Solution with lifetimes:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
What this means:
Part | Meaning |
|---|---|
| Declare a lifetime parameter 'a |
| x is a reference valid for lifetime 'a |
| y is a reference valid for lifetime 'a |
| Return value is valid for lifetime 'a |
In plain English:
"This function takes two references that are both valid for the same lifetime 'a, and returns a reference that is also valid for that lifetime."
Using it:
fn main() {
let string1 = String::from("long string");
let string2 = String::from("short");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is: {}", result);
}
Output:
The longest string is: long string
Example 2: Lifetime Mismatch
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("short");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest is: {}", result); // ERROR
}
Error:
error[E0597]: string2 does not live long enough
Why?
string2 is destroyed at the end of the inner scope.
result might point to string2, depending on which string is longer.
Rust prevents using result after string2 is destroyed.
Fixed version:
fn main() {
let string1 = String::from("long string");
let string2 = String::from("short");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest is: {}", result); // OK
}
Now both string1 and string2 live until the end of main, so result is safe to use.
Example 3: Structs with Lifetimes
If a struct holds references, it needs lifetime annotations.
Problem:
struct ImportantExcerpt {
part: &str, // ERROR: missing lifetime
}
Solution:
struct ImportantExcerpt<'a> {
part: &'a str,
}
What this means:
"An instance of ImportantExcerpt cannot outlive the reference it holds in part."
Using it:
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let excerpt = ImportantExcerpt {
part: first_sentence,
};
println!("Excerpt: {}", excerpt.part);
}
Output:
Excerpt: Call me Ishmael
Why it works:
novel lives for the entire main function.
first_sentence is a reference to part of novel.
excerpt.part points to first_sentence.
All lifetimes are valid throughout their usage.
Lifetime Elision Rules
Rust can infer lifetimes in simple cases, so you do not always need to write them explicitly.
Rule 1: Each input reference gets its own lifetime
fn first_word(s: &str) -> &str {
// Rust infers: fn first_word<'a>(s: &'a str) -> &'a str
s.split_whitespace().next().unwrap()
}
Rule 2: If there is one input lifetime, it is assigned to output
fn get_first(s: &str) -> &str {
// Rust infers: <'a>(s: &'a str) -> &'a str
&s[0..1]
}
Rule 3: If there is &self, its lifetime is assigned to output
impl<'a> ImportantExcerpt<'a> {
fn get_part(&self) -> &str {
// Rust infers the lifetime from &self
self.part
}
}
When you MUST write lifetimes:
Multiple input references.
Structs with references.
Complex return types.
Common Lifetime Patterns
1. Multiple lifetimes
fn longest_with_announcement<'a, 'b>(
x: &'a str,
y: &'a str,
ann: &'b str,
) -> &'a str {
println!("Announcement: {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
Meaning:
x and y have lifetime 'a.
ann has lifetime 'b, which is independent.
The return value has lifetime 'a.
2. Static lifetime
let s: &'static str = "I live forever";
The 'static lifetime means the reference is valid for the entire program duration.
Examples:
String literals like "hello"
Global constants
Visual Summary
┌─────────────────────────────────────────┐
│ Lifetime Problem │
├─────────────────────────────────────────┤
│ │
│ fn longest(x: &str, y: &str) -> &str │
│ ↑ │
│ Which lifetime? │
│ │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Lifetime Solution │
├─────────────────────────────────────────┤
│ │
│ fn longest<'a>( │
│ x: &'a str, ← same lifetime │
│ y: &'a str, ← same lifetime │
│ ) -> &'a str ← same lifetime │
│ │
│ "Return is valid as long as both │
│ x and y are valid" │
│ │
└─────────────────────────────────────────┘
Practice Exercise
Try to fix this code:
// This will not compile
fn get_first_word(s: &str) -> &str {
let words: Vec<&str> = s.split_whitespace().collect();
words[0]
}
Error:
error[E0515]: cannot return value referencing local variable words
Why?
words is created inside the function.
words[0] is a reference to data inside words.
words is destroyed when the function ends.
Returning words[0] would be a dangling reference.
Fixed version:
fn get_first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap()
}
Now the reference comes directly from s, not from a local variable.
Key Takeaways
Concept | Explanation |
|---|---|
Lifetime | How long a reference is valid |
'a | Lifetime parameter, similar to a generic type |
Dangling reference | Reference to destroyed data that Rust prevents |
Lifetime annotation | &'a T means reference valid for lifetime 'a |
Elision | Rust infers lifetimes in simple cases |
'static | Lives for the entire program |
When Do You Need Lifetimes?
You need lifetimes when:
Functions return references.
Structs hold references.
Multiple input references exist.
You do NOT need lifetimes when:
No references involved, using owned data like String or i32.
Simple cases where Rust can infer them automatically.
Blockchain Development Context
In Solana development with Anchor, you will see lifetimes in account structures:
pub fn initialize<'info>(
ctx: Context<'_, '_, '_, 'info, Initialize<'info>>
) -> Result<()> {
// 'info is the lifetime for account data
Ok(())
}
The 'info lifetime ensures that account references remain valid throughout the instruction execution. This becomes clearer after you have written a few Solana programs.
Conclusion
Lifetimes are Rust's way of guaranteeing memory safety at compile time. While they may seem complex at first, they prevent an entire class of bugs that plague other languages. With practice, lifetime annotations become second nature, and you will appreciate the safety they provide.
The key is to remember that lifetimes are about relationships between references, not about the data itself. They tell the compiler how long references should be valid relative to each other.
If you have questions or want more examples, feel free to discuss in the comments below.