Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 20 minutes
  • Examples run with Rust 1.99.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: A reference cannot outlive the value it refers to · A function cannot return a reference to its own local · Return owned data when the function makes it. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

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:

outlives.rs: a reference to a value of an inner block Rust · outlives.rs
// 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:

Interactive · step by step Why local does not live long enough in outlives.rs Step through main. The left column is main's own block, the right column the inner block and what it owns.
Why local does not live long enough in outlives.rs, step 6 of 6. Showing main's block, platform: &String (refers to local), local: a String (lives to the end of main). In focus: platform: &String, local: a String.refers tomain's blocklines 2 to 9platform: &Stringrefers to locallocal: a Stringlives to the end of main

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. A reference, declared. let platform: &String; declares a reference in main's own block. It has no value yet, and it lives until the end of main.
  2. A value in an inner block. The inner block makes local, a String holding "Platform 3". local lives only until the closing brace on line 7.
  3. The reference refers to it. platform = &local; borrows local. platform now refers to a value that lives less long than platform itself.
  4. The inner block ends. At the closing brace, local is dropped and its heap bytes are freed. platform still holds its address.
  5. rustc refuses. Line 8 would read through platform, a dangling reference. rustc reports E0597, local does not live long enough, and builds nothing.
  6. The fix. In outlives_fixed.rs, local is made in main's own block, before the reference, so it lives at least as long as platform and 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:

outlives_fixed.rs: the value made where the reference can use it Rust · outlives_fixed.rs
// 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

  1. Question 1 of 2 What does E0597, local does not live long enough, mean in outlives.rs?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: local is dropped at the end of the inner block while platform, which borrows it, is still to be used

    A reference may only be used while its value is alive. The inner block's brace ends local's life before the last use of platform.

  2. Question 2 of 2 Which change makes outlives.rs compile without copying the String?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Make local in main's own block, before platform takes the reference

    Fix the lives, not the types: the value must be made where it lives at least as long as every use of the reference.

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:

return_local.rs: a reference to a local, returned Rust · return_local.rs
// 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:

Interactive · message by message What shout() would hand back in return_local.rs Step through one call. Each message is a value or a reference handed over, or a request to the allocator.
What shout() would hand back in return_local.rs, step 6 of 6. Messages so far: main to shout(): &str "asha"; shout() to The allocator: a block for ASHA; shout() to The allocator: free the ASHA block; shout() to main: the String, moved.mainowns "ASHA"shout()returned, owns nothingThe allocatorthe heap&str "asha"a block for ASHAfree the ASHA blockthe String, moved

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The call. shout("asha") lends shout a &str that refers to the literal "asha", which lives as long as the program.
  2. A new String. let loud = name.to_uppercase(); asks the allocator for a block and fills it with "ASHA". loud, a local of shout, owns it.
  3. A reference to the local. &loud is a reference to loud, which is about to go out of scope with the rest of shout.
  4. The locals are dropped. When shout returns, its locals are dropped: loud gives its block back. A reference to it would refer to nothing.
  5. rustc refuses. So main would receive a dangling reference. rustc reports E0515, cannot return reference to local variable loud, and builds nothing.
  6. The fix returns the String. In owned.rs, shout returns String: the value moves to main, so the block is not freed at the return, and main owns "ASHA" from then on.

When the function has no input to borrow from at all, rustc stops even earlier, at the signature:

no_input.rs: a &String returned with nothing to borrow from Rust · no_input.rs
// 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

  1. Question 1 of 2 Why does rustc refuse to let shout return &loud?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: loud is a local of shout, dropped when shout returns, so the reference would refer to freed memory

    A function's locals and parameters are dropped before it returns, so a reference to one of them could never be used by the caller.

  2. Question 2 of 2 fn make_label(platform: u32) -> &String fails with E0106. Which of rustc's three suggestions fixes it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Return String instead of &String

    rustc lists the owned return value last, but for a function that makes its result it is the only fix.

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:

owned.rs: owned results, and a borrow from an input Rust · owned.rs
// 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

  1. Question 1 of 2 Which signature fits a function that joins a first name and a last name into a full name?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: fn full_name(first: &str, last: &str) -> String

    Ask where the result comes from: new data is returned owned; a part of an input may be returned as a borrow.

  2. Question 2 of 2 When may a function return &'static str?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: When the text is fixed when the program is built, such as a string literal

    'static means the data lives for the whole program. Literals do; anything built while the program runs does not, unless it is leaked on purpose.

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.

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.

  1. Question 1 of 9 For a reference to a value to be allowed, what must be true?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: The value lives at least as long as the reference is used

    Only the overlap of lives matters: every use of the reference must come before its value is dropped.

  2. Question 2 of 9 Does this compile?

    Read the code, then choose one answer.

    let r;
    {
        let x = 5;
        r = &x;
        println!("{r}");
    }
    Show the answer: Question 2 of 9

    Answer: Yes, because r is last used inside the block, while x is still alive

    rustc 1.99.0 accepts it. Add println!("{r}") after the brace and it reports E0597, x does not live long enough.

  3. Question 3 of 9 Why can a test suite not show that C code returning the address of a local array is safe?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: Its behaviour is undefined, so a passing test only shows what one build did on one run

    The SEI CERT C rules quote the C standard: referring to an object outside of its lifetime is undefined behaviour. Rust turns the same mistake into a compile error instead.

  4. Question 4 of 9 What does error E0515 report?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: A function returns a reference to data that the function itself owns

    The fix is almost always to return the owned value, which moves to the caller instead of being dropped.

  5. Question 5 of 9 Why does fn make_label(platform: u32) -> &String fail at the signature, before rustc looks at the body?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: A returned reference must borrow from an input, and the only input is a u32 passed by value

    The E0106 page states the rule: an elided output lifetime needs exactly one input lifetime, or a method's self.

  6. Question 6 of 9 What does rustc say about this function?

    Read the code, then choose one answer.

    fn longest_line(text: String) -> &str {
        text.lines().max_by_key(|l| l.len()).unwrap_or("")
    }
    Show the answer: Question 6 of 9

    Answer: E0106, because text is owned by the function and the signature has no borrowed input; take text as &str

    rustc 1.99.0 reports E0106, missing lifetime specifier. A parameter taken by value is a local like any other.

  7. Question 7 of 9 What does returning a String from a function cost?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: A move of its 24-byte handle; the text is not copied

    Returning owned data is cheap, which is why it is the default fix for a function that makes its result.

  8. Question 8 of 9 Which of these functions can return a &str tied to their input, allocating nothing?

    Choose every answer that is right.

    Show the answer: Question 8 of 9

    Answer:

    • fn trim_spaces(text: &str)
    • fn first_word(text: &str)

    A borrowed result must already exist in an input. Anything the function builds, it returns owned.

  9. Question 9 of 9 A teammate fixes E0106 on fn make_label(platform: u32) -> &String by writing &'static String. Why will that not work?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: The label is built at run time with format!, so no &'static reference to it can exist without leaking it

    Return String. A 'static reference is right for literals and for data leaked on purpose, never for a value a function has just built.

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.

  1. 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?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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?

    Model answer, Pro. First step and follow-ups

  3. Fresher to mid

    let host = config.get("host").unwrap().to_lowercase().trim(); is refused with E0716 because host is used on the next line. Explain the error, give the smallest fix, and say when you would restructure the code instead.

    Model answer, Pro. First step and follow-ups

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_word does.

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

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.