Rust Lifetimes Basics
A lifetime is the stretch of code where a reference stays usable. It ends where the value the reference points at is dropped.
A reference that outlives its value
A value is dropped at the end of the block that made it. This program borrows a String inside a block, then uses the borrow after the block ends. The borrow would point at a value that no longer exists. A reference left pointing at a dropped value is called a dangling reference, and Rust refuses to compile the program.
fn main() {
let answer;
{
let text = String::from("inside");
answer = &text;
}
println!("answer = {}", answer);
}One input needs no mark
This function takes one borrowed text and hands back its first letter. With one input, the answer can borrow from nothing else.
fn first_letter(text: &str) -> &str {
&text[..1]
}
fn main() {
println!("first = {}", first_letter("rust"));
}Two inputs raise a question
This function hands back one of its two borrowed texts. The answer could come from x or from y, and the signature does not say which. The compiler stops and asks. It calls the missing piece a lifetime specifier.
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let a = String::from("long string");
let b = "short";
println!("longest = {}", longest(&a, b));
}The mark is a name tag
The mark 'a is that specifier, written in. It names one shared stretch of code, and it sits on both inputs and on the answer. All three references share the one stretch.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let a = String::from("long string");
let b = "short";
println!("longest = {}", longest(&a, b));
}The mark catches the bug
The shorter text is made inside a block here, and the answer is printed after the block. The compiler cannot tell which input the answer came from, and the mark says it may come from either one. So the answer may not be used after the short text is dropped, and the compiler refuses the program.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let a = String::from("long string");
let winner;
{
let b = String::from("short");
winner = longest(&a, &b);
}
println!("winner = {}", winner);
}Example
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let a = String::from("the long way home");
let b = String::from("short cut");
let winner = longest(&a, &b);
println!("winner = {}", winner);
{
let c = String::from("a brief note");
let d = String::from("tiny");
let local = longest(&c, &d);
println!("local winner = {}", local);
}
println!("still here = {}", winner);
}Key points
- References under one name share one stretch, and the answer stays usable only while its shortest-lived input does.
- Most functions write no lifetime mark at all.
- The compiler fills the common patterns in by itself.
In this example, longest is called on two pairs of texts. a and b live until the end of main, so winner is still safe to print in the last line. c and d live only inside the block, and local is printed inside the block, while both still live. Every borrowed answer is used inside the stretch of the values behind it. Rust Lifetimes, in the Intermediate course, goes deeper from here — the rules the compiler uses to fill marks in by itself, and lifetimes written on structs.
Note
A lifetime only describes a stretch of code. It never makes a value live longer.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
In fn longest<'a>(x: &'a str, y: &'a str) -> &'a str, what does 'a promise?