Lifetimes
## Learning Objectives
- Understand what lifetimes are
- Learn lifetime annotations
- Master elision rules
- Use 'static lifetime
- Avoid dangling references
## What are Lifetimes?
Lifetimes ensure references are always valid. The compiler checks that references don't outlive the data they refer to.
```rust
fn main() {
let r;
{
let x = 5;
r = &x; // Error: x doesn't live long enough
}
println!("{}", r);
}
```
## Lifetime Annotations
### Syntax
```rust
&'a type // Reference with lifetime 'a
&'a mut type // Mutable reference with lifetime 'a
```
### Named Lifetimes
```rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let s1 = String::from("long string");
let result;
{
let s2 = String::from("xyz");
result = longest(s1.as_str(), s2.as_str());
println!("{}", result);
}
// result would be invalid here if used
}
```
## Lifetime Elision
The compiler infers lifetimes in common cases.
### Elision Rules
1. Each reference parameter gets its own lifetime
2. If there's exactly one input lifetime, it's assigned to output
3. If there's a `&self` or `&mut self`, it's assigned to output
### Examples
```rust
// Rule 1: Each parameter gets a lifetime
fn foo(x: &str, y: &str) -> &str { } // Compiler sees: foo<'a, 'b>(x: &'a str, y: &'b str) -> &str
// Rule 2: Single input lifetime -> output
fn first_word(s: &str) -> &str { } // Compiler sees: first_word<'a>(s: &'a str) -> &'a str
// Rule 3: &self -> output lifetime
impl Config {
fn get_name(&self) -> &str { } // Compiler sees: get_name<'a>(&'a self) -> &'a str
}
```
### When Elision Doesn't Work
```rust
// Error: return reference with unclear lifetime
fn longest(x: &str, y: &str) -> &str { // Compiler error!
if x.len() > y.len() { x } else { y }
}
// Solution: add explicit lifetime
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
```
## Structs with References
Structs holding references need lifetime annotations:
```rust
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael...");
let first_sentence = novel.split('.').next().unwrap();
let excerpt = ImportantExcerpt {
part: first_sentence,
};
}
```
### Method Lifetimes
```rust
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.part
}
}
```
## Static Lifetime
`'static` means the reference lives for the entire program:
```rust
let s: &'static str = "I live forever";
fn main() {
// String literals have 'static lifetime
let s = "hello"; // &'static str
// &'static references can outlive any data
}
```
### When to Use 'static
```rust
// Error messages
fn generic() { } // T: 'static sometimes needed
// Global constants
const GREETING: &str = "Hello"; // &'static
// Box with static data
fn get_static_string() -> &'static str {
"a static string"
}
```
## Lifetime Subtyping
One lifetime outlives another:
```rust
struct Context<'a, 'b> {
part: &'a str,
other: &'b str,
}
fn main() {
let s1 = String::from("long string");
let s2 = String::from("short");
{
let excerpt = Context {
part: s1.as_str(),
other: s2.as_str(),
};
} // excerpt dropped here
}
```
## Multiple Lifetimes
```rust
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
if x.len() > y.len() {
x
} else {
x // Return 'a anyway (could return y with &'b)
}
}
```
## Lifetime in Methods
```rust
struct Owner(i32);
impl Owner {
fn add_one<'a>(&'a mut self) {
self.0 += 1;
}
fn print<'a>(&'a self) {
println!("{}", self.0);
}
}
fn main() {
let mut owner = Owner(10);
owner.add_one();
owner.print();
}
```
## Trait Bounds with Lifetimes
```rust
use std::fmt::Display;
fn longest_with_anouncement<'a, T>
where
T: 'a,
T: Display,
{
// ...
}
```
## Common Patterns
### Reference in Struct
```rust
struct Ref<'a, T> {
data: &'a T,
}
fn main() {
let x = 5;
let r = Ref { data: &x };
println!("{:?}", r.data);
}
```
### Multiple References
```rust
struct MultiRef<'a, 'b, 'c, T> {
first: &'a T,
second: &'b T,
third: &'c T,
}
```
## Summary
- Lifetimes prevent dangling references
- Annotations: `&'a T` means reference lives at least 'a
- Elision rules handle common cases automatically
- Structs holding references need lifetime parameters
- `'static` means reference lives for entire program
- Multiple lifetimes when needed
- Methods use `impl<'a>` syntax
- Lifetime bounds: `T: 'a`
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →