Lifetimes in Rust
Lifetimes are Rust's way of ensuring references are always valid. They're like compile-time labels that tell the compiler how long references live.
You rarely need to write lifetime annotations explicitly. The compiler figures them out most of the time through lifetime elision rules.
fn main() {
let string1 = String::from("long string");
let result;
{
let string2 = String::from("hi");
result = longest(string1.as_str(), string2.as_str());
println!("Longest: {}", result);
}
// result would be invalid here because string2 is dropped
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
Try it Yourself ->
Lifetime Elision Rules
The compiler applies three rules to figure out lifetimes automatically. First, each parameter gets its own lifetime. Second, if there's exactly one input lifetime, it's assigned to all outputs. Third, if there's a &self or &mut self, its lifetime is assigned to outputs.
// These two signatures are equivalent:
// With explicit lifetimes:
fn first_word_explicit<'a>(s: &'a str) -> &'a str {
s.split_whitespace().next().unwrap_or("")
}
// With elision (compiler figures out lifetimes):
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
fn main() {
let sentence = String::from("hello world");
let word = first_word(&sentence);
println!("First word: {}", word);
}
Try it Yourself ->
Function Signatures with Lifetimes
When functions return references, you often need to specify which input the output is connected to. This prevents dangling references.
fn longest_word<'a>(sentence: &'a str) -> &'a str {
let mut longest = "";
for word in sentence.split_whitespace() {
if word.len() > longest.len() {
longest = word;
}
}
longest
}
fn main() {
let text = String::from("the quick brown fox jumps");
let word = longest_word(&text);
println!("Longest word: {}", word);
}
Try it Yourself ->
Struct Lifetimes
When structs hold references, you need lifetime annotations to ensure the data outlives the struct.
struct Excerpt<'a> {
text: &'a str,
}
impl<'a> Excerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce(&self, announcement: &str) -> &str {
println!("Attention: {}", announcement);
self.text
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let excerpt = Excerpt {
text: &novel[..15],
};
println!("Excerpt: {}", excerpt.text);
println!("Level: {}", excerpt.level());
}
Try it Yourself ->
The 'static Lifetime
The 'static lifetime means the reference lives for the entire program. String literals have 'static lifetime because they're embedded in the binary.
fn main() {
let s: &'static str = "I live forever";
println!("{}", s);
// String literals are always 'static
let literal = "This is also 'static";
println!("{}", literal);
}
Try it Yourself ->
Why Lifetimes Matter
Lifetimes prevent dangling references, which cause undefined behavior in other languages. Rust catches these bugs at compile time, making your code safer.
You only need explicit annotations when the compiler can't figure out lifetimes automatically. In most cases, especially with function signatures, the compiler handles it for you.