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
- Returning
&Stringfrom a function that builds a new string (E0106 or E0515). ReturnString. - Keeping a reference to a value from an inner block after the block (E0597).
- Adding
'staticbecause the compiler mentions it. For these errors it's almost never what you want; return an owned value.
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