Why lifetimes exist
A reference (&T) is a pointer that does not own what it points at. Something else owns the value, and when that owner goes out of scope the value is dropped. If a reference is still around after that, it points at freed memory: a dangling reference. In C and C++ this compiles and then crashes, or worse, silently reads garbage. Rust refuses to compile it.
A lifetime is the stretch of code during which a reference is valid. The compiler part that checks lifetimes is the borrow checker (you met it in Module 03). Its one rule: a reference must never outlive the value it borrows from. Most of the time it works out every lifetime on its own and you never write one. You only annotate when the compiler cannot tell which input a returned reference came from.
fn main() {
let owner = String::from("borrowed text");
let r = &owner; // r borrows from owner
println!("{}", r); // fine: owner is still alive
println!("{} bytes", r.len());
} // r stops being used, then owner is dropped hereborrowed text
13 bytesThe reference is used only while the owner is alive, so there is nothing for the borrow checker to complain about.
Move let owner into an inner block { ... } and keep the println! outside it. Read the error before looking at the next card.
E0597: a reference that outlives its value
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r = {}", r);
}error[E0597]: `x` does not live long enough
--> main.rs:5:13
|
4 | let x = 5;
| - binding `x` declared here
5 | r = &x;
| ^^ borrowed value does not live long enough
6 | }
| - `x` dropped here while still borrowed
7 | println!("r = {}", r);
| - borrow later used herex lives only inside the inner block and is dropped at the closing brace on line 6. r is used on line 7, after that. The error message literally draws the three facts: where the borrow starts, where the owner dies, and where the borrow is used too late.
Make the owner live at least as long as the reference: declare it in the outer scope (or copy the value out instead of borrowing it).
fn main() {
let x = 5;
let r = &x;
println!("r = {}", r);
}fn main() {let x = 5;let r = &x;println!("r = {}", r);}
x is created. Its lifetime runs from here to the closing brace of main.
x | 5 |
All 4 steps as a table
| Step | Line | What happened | Variables now |
|---|---|---|---|
| 1 | 2 | x is created. Its lifetime runs from here to the closing brace of main. | x = 5 |
| 2 | 3 | r borrows x. The borrow checker records that this borrow must end before x does. | x = 5 r = &x |
| 3 | 4 | Last use of r. The borrow ends here, well inside the lifetime of x, so the program is accepted. | x = 5 r = &x |
| 4 | 5 | x is dropped. No reference to it is alive any more. |
