Rust Lifetimes
Memory safety through lifetime annotations
⏰ What are Rust Lifetimes?
Lifetimes ensure references are valid for as long as needed. They prevent dangling pointers and memory safety issues by tracking how long data lives, making Rust memory-safe without garbage collection.
// Function with lifetime annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
Lifetime Concepts
Implicit Lifetimes
Rust infers most lifetimes automatically
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
Explicit Lifetimes
Manual annotations when needed
fn compare<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
Struct Lifetimes
Lifetimes in struct definitions
struct ImportantExcerpt<'a> {
part: &'a str,
}
Static Lifetime
Lives for entire program duration
let s: &'static str = "Hello, world!";
🔹 Basic Lifetime Example
Understanding how lifetimes work with references:
fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is: {}", result);
}
// string2 goes out of scope here, but that's okay
// because result was used before this point
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
Output:
The longest string is: long string is long
🔹 Lifetime Elision Rules
Rust can infer lifetimes in many cases:
🔸 Rule 1: Each parameter gets its own lifetime
// This function:
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
// Is equivalent to:
fn first_word<'a>(s: &'a str) -> &'a str {
s.split_whitespace().next().unwrap_or("")
}
🔸 Rule 2: If one input lifetime, output gets same lifetime
// These are equivalent:
fn get_first_char(s: &str) -> &str { &s[0..1] }
fn get_first_char<'a>(s: &'a str) -> &'a str { &s[0..1] }
🔹 Structs with Lifetimes
When structs hold references, they need lifetime annotations:
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
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);
println!("Level: {}", excerpt.level());
}
Output:
Excerpt: Call me Ishmael
Level: 3
🔹 Static Lifetime
The 'static lifetime lives for the entire program:
// String literals have 'static lifetime
let s: &'static str = "I have a static lifetime.";
// Static variables
static HELLO_WORLD: &str = "Hello, world!";
fn main() {
println!("{}", s);
println!("{}", HELLO_WORLD);
// This function requires 'static lifetime
let result = takes_static_str("This is a string literal");
println!("{}", result);
}
fn takes_static_str(s: &'static str) -> &'static str {
s
}
Output:
I have a static lifetime.
Hello, world!
This is a string literal
🔹 Multiple Lifetime Parameters
Functions can have multiple lifetime parameters:
fn announce_and_return_first<'a, 'b>(
announcement: &'a str,
x: &'b str,
y: &'b str,
) -> &'b str {
println!("Attention please: {}", announcement);
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let announcement = "Ladies and gentlemen";
let string1 = String::from("long string is long");
let string2 = String::from("short");
let result = announce_and_return_first(
announcement,
string1.as_str(),
string2.as_str(),
);
println!("The longest is: {}", result);
}
Output:
Attention please: Ladies and gentlemen
The longest is: long string is long