RustArena

Step 6 of 7

No dangling references

A dangling reference points at memory that has already been freed. In C you can return a pointer to a local variable: it compiles, and reading through it later gives garbage, a crash, or a security hole. Rust's second borrowing rule is: a reference must never outlive the value it points to. The compiler checks it everywhere.

Returning a reference to a local: E0106

fn make_label() -> &String {
    let label = String::from("VIP");
    &label
}

fn main() {
    println!("{}", make_label());
}
error[E0106]: missing lifetime specifier
 --> src/main.rs:1:20
  |
1 | fn make_label() -> &String {
  |                    ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: instead, you are more likely to want to return an owned value
  |
1 - fn make_label() -> &String {
1 + fn make_label() -> String {

label is dropped at the end of make_label, so a reference to it would dangle the moment the function returns. The help line says it exactly: a returned reference has to borrow from something, and there's nothing here to borrow from. ("Lifetime" is the compiler's word for how long a reference stays valid; you'll learn to write lifetimes in their own module.)

The fix isn't a lifetime: it's to return the owned value. Ownership moves to the caller, so nothing dangles:

fn make_label(vip: bool) -> String {
    let label = if vip { String::from("VIP") } else { String::from("standard") };
    label    // ownership moves to the caller
}

fn main() {
    let a = make_label(true);
    let b = make_label(false);
    println!("{} / {}", a, b);
}
VIP / standard

If the function has a reference parameter, the error is different but means the same thing:

fn shout(text: &String) -> &String {
    let loud = text.to_uppercase();
    &loud
}

fn main() {
    println!("{}", shout(&String::from("hi")));
}
error[E0515]: cannot return reference to local variable `loud`
 --> src/main.rs:3:5
  |
3 |     &loud
  |     ^^^^^ returns a reference to data owned by the current function

Returning a reference is fine when it points into something the caller gave you, because that value lives in the caller:

fn first_item(list: &Vec<String>) -> &String {
    &list[0]           // borrows from list, which the caller owns
}

fn main() {
    let items = vec![String::from("sword"), String::from("shield")];
    let first = first_item(&items);
    println!("first: {}", first);
}
first: sword

A reference outliving a scope: E0597

fn main() {
    let r;
    {
        let s = String::from("hi");
        r = &s;
    }
    println!("{}", r);
}
error[E0597]: `s` does not live long enough
 --> src/main.rs:5:13
  |
4 |         let s = String::from("hi");
  |             - 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
let r;
{
    let s = String::from("hi");   ── s lives ──┐
    r = &s;                                    │
}                                 ◄────────────┘  s dropped here
println!("{}", r);                ← r would point at freed memory

Fix it by making the value live as long as the reference (declare s in the outer scope), or by having r own a value instead of borrowing one.

Workplace note: this check is what removes use-after-free from safe Rust. In practice it gives a simple rule for signatures: functions that create new data return owned values (String, Vec<T>); functions that look something up in their input can return a reference into it.

Common mistakes

Your task

make_badge builds a new String and tries to return a reference to it (E0515). Fix the function so it gives the caller ownership of the badge. The program should print:

[7] Ferris
Ferris is still ours

Challenge 2

winner borrows from final_round, which is dropped at the end of the inner block (E0597). Fix it so it prints:

winner: Ferris

Either make final_round live long enough, or give winner its own copy of the name.

Challenge 3

Write longest, which borrows a vector of words and returns a reference to the longest one (by .len(); the first one on a tie). It returns a reference into the vector, not a copy. You can assume the vector isn't empty.

Previous: Borrows end at their last use Next: Choosing how to pass a value