RustArena

Step 1 of 7

Why lifetimes exist: scopes and dangling references

A reference points at a value that someone else owns. If the owner is dropped while the reference is still in use, the reference points at freed memory: a dangling reference. In C and C++ that's one of the most common causes of crashes and security holes. Rust rules it out completely, at compile time, with the borrow checker.

The borrow checker compares two stretches of code:

  1. How long the value lives: from where it's created to where it's dropped (the end of its scope, or where it's moved away).
  2. How long the reference is used: from where it's created to its last use.

The second must fit inside the first. That stretch where a reference must stay valid is its lifetime. Here's the classic failure:

fn main() {
    let r;
    {
        let s = String::from("crab");
        r = &s;
    }
    println!("{r}");
}
error[E0597]: `s` does not live long enough
 --> src/main.rs:5:13
  |
4 |         let s = String::from("crab");
  |             - binding `s` declared here
5 |         r = &s;
  |             ^^ borrowed value does not live long enough
6 |     }
  |     - `s` dropped here while still borrowed
7 |     println!("{r}");
  |                - borrow later used here

Draw the two stretches next to the code and the problem is plain:

let r;                              r's use ----------------+
{                                                           |
    let s = String::from("crab");   s lives --+             |
    r = &s;                                   |             |
}                                   s dropped +             |
println!("{r}");                    last use of r ----------+

s is gone at the }, but r is still used after it. The error's four labels tell exactly this story: where s was declared, where it was borrowed, where it was dropped, and where the borrow was used too late. Read every label, not just the first line: they point at the three places you might change.

Three ways to fix it

  1. Make the value live longer: move let s out to the outer scope.
  2. Use the reference sooner: move the println! inside the block, before s is dropped.
  3. Don't borrow; own: make r a String (r = s; moves it out, or r = s.clone();).
fn main() {
    let s = String::from("crab");
    let r;
    {
        r = &s;
        println!("inside: {r}");
    }
    println!("outside: {r}");
}
inside: crab
outside: crab

Lifetimes are checked, not chosen

A lifetime is not something you set, and nothing you write changes when a value is dropped. Values are dropped where the ownership rules say. Lifetimes are the compiler's way of checking that references respect that. Most of the time it works them out on its own, just like it infers most types. In the next steps you'll see the few places where it needs you to describe how references relate.

Remember from the borrowing module that a reference's lifetime ends at its last use, not at the end of its scope. That's why this is fine: first isn't used after the push.

fn main() {
    let mut scores = vec![10, 20];
    let first = &scores[0];
    println!("first: {first}");
    scores.push(30);
    println!("{:?}", scores);
}
first: 10
[10, 20, 30]

Common mistakes

Your task

This program doesn't compile: longest_name borrows from names, which is dropped at the end of the inner block. Fix it by making names live long enough (don't copy the string). It should print Longest: Corro the Unsafe.

Challenge 2

This program tries to keep references to labels that are created inside the loop, so each label is dropped at the end of its round. Fix it the third way, by owning the data: make picks a Vec<String> and push the labels themselves. It should print ["round 1", "round 2", "round 3"].

Challenge 3

Borrowing works great when the data lives long enough. Read all of stdin into one String, then find the longest line as a &str that borrows from that String (no copying). Count length in characters (.chars().count()); on a tie, keep the first. Print:

longest: <line> (<n> chars)

For empty input, print longest: (0 chars) (the longest line is empty, so nothing goes between the two spaces).

Next: Lifetime annotations on functions