Rust (2024 edition) Module 3 – Ownership, borrowing and slices
Dangling references and returning owned data
Why Rust refuses a reference that would outlive its value, how to fix E0597, E0515 and E0106, and when a function should return owned data instead.
What you will learn
- Explain why a reference cannot outlive the value it refers to, and read error E0597
- Fix 'cannot return reference to local variable' and 'missing lifetime specifier' by returning owned data
- Decide whether a function should return an owned value or a reference into one of its inputs
- Diagnose and fix error E0716, a temporary value dropped while it is still borrowed
Before you start
What you’ll learn · Before you start
What you will learn
- Explain why a reference cannot outlive the value it refers to, and read error E0597
- Fix 'cannot return reference to local variable' and 'missing lifetime specifier' by returning owned data
- Decide whether a function should return an owned value or a reference into one of its inputs
- Diagnose and fix error E0716, a temporary value dropped while it is still borrowed
Before you start
On this page
A C function can write “Platform 3” into a 16-byte local array and return the array’s address, handing its caller a pointer into a stack frame that no longer exists; the SEI CERT C rules list it as undefined behaviour. Safe Rust refuses the same function written with a reference: a reference that could outlive the value it refers to does not compile.
This lesson shows the two ways code tries to make dangling references, a reference kept after the block that owns its value has ended and a reference returned from the function that made its value, and the fix most of them need: returning owned data. The Rust programs here ran on Rust 1.99.0.
A reference cannot outlive the value it refers to
The Rust book makes it a promise of the compiler: no data goes out of scope while a reference to it can still be used. Rust has no dangling reference: A reference to memory whose value has been dropped or has moved away. Safe Rust never lets one exist: the compiler refuses any reference that could outlive the value it refers to., because the borrow checker compares how long each reference is used with how long its value lives. Here a reference in the outer block is pointed at a value of an inner block:
// A reference kept in an outer variable, to a value of an inner block.
fn main() {
let platform: &String;
{
let local = String::from("Platform 3");
platform = &local;
}
println!("board at {platform}");
} Output (exit status 1)
Printed as an error (standard error)
error[E0597]: `local` does not live long enough
--> outlives.rs:6:20
|
5 | let local = String::from("Platform 3");
| ----- binding `local` declared here
6 | platform = &local;
| ^^^^^^ borrowed value does not live long enough
7 | }
| - `local` dropped here while still borrowed
8 | println!("board at {platform}");
| -------- borrow later used here
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0597`.
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O outlives.rs && ./outlives
E0597 reads as a short story: local was declared on line 5, borrowed on line 6, dropped at the brace on line 7 while
still borrowed, and the borrow is used later, on line 8. Step through the two blocks:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- A reference, declared.
let platform: &String;declares a reference inmain's own block. It has no value yet, and it lives until the end ofmain. - A value in an inner block. The inner block makes
local, aStringholding "Platform 3".locallives only until the closing brace on line 7. - The reference refers to it.
platform = &local;borrowslocal.platformnow refers to a value that lives less long thanplatformitself. - The inner block ends. At the closing brace,
localis dropped and its heap bytes are freed.platformstill holds its address. - rustc refuses. Line 8 would read through
platform, a dangling reference. rustc reports E0597,localdoes not live long enough, and builds nothing. - The fix. In outlives_fixed.rs,
localis made inmain's own block, before the reference, so it lives at least as long asplatformand the borrow is allowed.
The fix the error index gives is to let the value live as long as the reference, here by making local in the outer
block:
// The fix the error index gives: make the value live as long as the reference, here by declaring it in the outer block.
fn main() {
let local = String::from("Platform 3");
let platform: &String = &local;
println!("board at {platform}");
} Output
board at Platform 3
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O outlives_fixed.rs && ./outlives_fixed
Checkpoint
Check: A reference that outlives its value
A function cannot return a reference to its own local
A function’s locals and parameters are dropped when it returns, so a reference to one of them would dangle the moment
the caller got it. This shout makes an upper-case copy of its input and tries to return a reference to the copy:
// shout() makes a new String, then tries to hand out a reference to it.
fn shout(name: &str) -> &str {
let loud = name.to_uppercase();
&loud
}
fn main() {
println!("{}", shout("asha"));
} Output (exit status 1)
Printed as an error (standard error)
error[E0515]: cannot return reference to local variable `loud` --> return_local.rs:4:5 | 4 | &loud | ^^^^^ returns a reference to data owned by the current function error: aborting due to 1 previous error For more information about this error, try `rustc --explain E0515`.
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O return_local.rs && ./return_local
E0515 says it in a line: the function returns a reference to data owned by the current function. Step through the call to see where that data goes:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The call.
shout("asha")lendsshouta&strthat refers to the literal "asha", which lives as long as the program. - A new String.
let loud = name.to_uppercase();asks the allocator for a block and fills it with "ASHA".loud, a local ofshout, owns it. - A reference to the local.
&loudis a reference toloud, which is about to go out of scope with the rest ofshout. - The locals are dropped. When
shoutreturns, its locals are dropped:loudgives its block back. A reference to it would refer to nothing. - rustc refuses. So
mainwould receive a dangling reference. rustc reports E0515, cannot return reference to local variableloud, and builds nothing. - The fix returns the String. In owned.rs,
shoutreturnsString: the value moves tomain, so the block is not freed at the return, andmainowns "ASHA" from then on.
When the function has no input to borrow from at all, rustc stops even earlier, at the signature:
// make_label() has no input to borrow from, so its &String could only point at its own local.
fn make_label(platform: u32) -> &String {
let label = format!("Platform {platform}");
&label
}
fn main() {
println!("{}", make_label(3));
} Output (exit status 1)
Printed as an error (standard error)
error[E0106]: missing lifetime specifier
--> no_input.rs:2:33
|
2 | fn make_label(platform: u32) -> &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: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
|
2 | fn make_label(platform: u32) -> &'static String {
| +++++++
help: instead, you are more likely to want to change the argument to be borrowed...
|
2 | fn make_label(platform: &u32) -> &String {
| +
help: ...or alternatively, you might want to return an owned value
|
2 - fn make_label(platform: u32) -> &String {
2 + fn make_label(platform: u32) -> String {
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0106`.
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O no_input.rs && ./no_input
E0106, missing lifetime specifier, comes with three suggestions, and only the last fits here. &'static String would
need a String that lives for the whole program, which format! does not make. Borrowing the argument would change
the input and still leave label local. Returning the owned String is the fix. A third case involves a value with no
name at all: a reference to a temporary, which is dropped at the end of its statement, is error E0716.
Checkpoint
Check: Returning a reference to a local
Return owned data when the function makes it
The rule that sorts out almost every case is about where the result comes from. Returning owned data, a String or a
Vec, is right when the function makes the data; a reference is right only when the result points into an input:
// Return what the function makes; return a borrow only when it points into an input.
fn make_label(platform: u32) -> String {
format!("Platform {platform}")
}
fn shout(name: &str) -> String {
name.to_uppercase()
}
fn first_word(text: &str) -> &str {
text.split_whitespace().next().unwrap_or("")
}
fn station_code() -> &'static str {
"PUNE"
}
fn main() {
let label = make_label(3);
let loud = shout("asha");
let line = String::from("Kochi Express, platform 3");
let word = first_word(&line);
println!("{label} | {loud} | {word} | {}", station_code());
} Output
Platform 3 | ASHA | Kochi | PUNE
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O owned.rs && ./owned
| The result | Return | Example |
|---|---|---|
| New data, made inside the function | An owned value: String, Vec<T> |
make_label, shout |
| A part of an input | A reference tied to that input: &str, &[T] |
first_word(&line) |
| Text fixed when the program is built | &'static str |
station_code |
Returning owned data costs nothing extra: the String moves to the caller as a 24-byte handle, as the lesson on moves
showed, and its text is never copied. first_word may return &str because its result is a slice of text, and the
compiler ties the result’s life to the caller’s line on its own. When a function only sometimes makes new text, the
standard library’s Cow type can hold either a borrow of the input or an owned String.
Key idea
If a function makes the data it returns, return it owned, and the move hands it to the caller for the cost of a handle; return a reference only when the result points into one of the function’s inputs.
Checkpoint
Check: Owned results or borrowed ones
Temporaries that die too soon, and pointers the checker does not see Pro: opens with a Pro, Premium or Ultimate pass
Not every dangling reference has a name. This part reproduces E0716, a borrow of a temporary that is dropped at the end of its statement, shows the rule that sometimes keeps a temporary alive, and what rustc does with a raw pointer to a local, which the borrow checker does not track.
In this part:
- A temporary is dropped at the end of its statement: E0716
- A raw pointer to a local compiles, with a warning since Rust 1.91
- A borrowed result keeps its input borrowed at the call site
- Two borrowed inputs and a borrowed result: E0106 again
6 runnable examples (Python, Rust) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Porting a C function that returns a stack buffer, measured Pro: opens with a Pro, Premium or Ultimate pass
C programmers meet this error first when they port a function that returns a pointer to a local buffer. This part builds the C version, ports it to Rust in two ways, counts what each port allocates, and ends with drills and a small coding task.
In this part:
- The C version builds, with one warning
- Two Rust ports, and what each one allocates
- Borrowed when unchanged, owned when changed: Cow
- Drills: name the error and the smallest fix
- Exercise: initials and first_word
5 runnable examples (C, Python, Rust) · about 8 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Practise: dangling references and owned results
These questions are about references that outlive their values, references returned from functions, and choosing an owned result. Every choice says why it is right or what it misses.
Practice that adapts to you
Practise this lesson
9 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): what is a dangling reference, and how does Rust prevent one?
A dangling reference refers to memory whose value has been dropped or moved away, such as a local of a function that has returned; in C and C++ reading through one is undefined behaviour. Safe Rust never lets one exist: the borrow checker compares how long each reference is used with how long its value lives, and refuses the program otherwise, with errors such as E0597 and E0515. A good answer adds the usual fix: when a function makes its result, return it owned, so that it moves to the caller instead of being dropped.
-
Fresher to mid
Design
fn normalise_name(input: &str) -> ?for a sign-up form. Most names arrive clean; some need trimming and a capital first letter. Do you return &str, String or something else, and why? -
Mid to senior
A C library function returns a
const char *that points at a static buffer, which its next call overwrites. You are writing the Rust wrapper. What signature do you give it, and why not one that returns &'static str? -
Fresher to mid
let host = config.get("host").unwrap().to_lowercase().trim();is refused with E0716 becausehostis used on the next line. Explain the error, give the smallest fix, and say when you would restructure the code instead.
Key takeaways
- A reference must not outlive its value: a borrow of an inner block’s value, used after the block, is E0597.
- A function’s locals are dropped when it returns, so returning a reference to one is E0515, and a borrowed result with no input to borrow from is E0106.
- Returning owned data, such as a
String, is the fix whenever the function makes the result; the move costs a handle. - Return a reference only when the result points into an input, as
first_worddoes.
Terms in this lesson
- dangling reference
- A reference to memory whose value has been dropped or has moved away. Safe Rust never lets one exist: the compiler refuses any reference that could outlive the value it refers to.
References
- The Rust Programming Language: References and Borrowing (Dangling References) (The Rust Project)
- Rust error codes index: E0597 (The Rust Project)
- Rust error codes index: E0515 (The Rust Project)
- Rust error codes index: E0106 (The Rust Project)
- Rust error codes index: E0716 (The Rust Project)
- The Rust Reference: Destructors (temporary lifetime extension) (The Rust Project)
- Announcing Rust 1.91.0 (dangling pointers from locals) (The Rust Project)
- The Rust Reference: Pointer types (raw pointers) (The Rust Project)
- The Rust Standard Library: std::fmt::Write (The Rust Project)
- SEI CERT C Coding Standard: DCL30-C, Declare objects with appropriate storage durations (Carnegie Mellon University Software Engineering Institute)
Report a problem with this lesson
Kept only in this browser. Your Learn progress