Rust Error E0515: Cannot Return Reference to Local Variable
E0515 means: your function tries to return a reference (&) to something it created itself. Everything a function creates is cleaned up when the function returns, so the reference would point at nothing. Nearly every Rust beginner hits this one, and the fix is usually to remove a single & and return the value itself. (This page is part of Rust compiler errors explained.)
The error message
Section titled “The error message”error[E0515]: cannot return reference to local variable `greeting` --> src/main.rs:3:5 |3 | &greeting // ❌ greeting is dropped when we return | ^^^^^^^^^ returns a reference to data owned by the current functionThis is the program that produces it:
fn make_greeting(name: &str) -> &str { let greeting = format!("Hello, {name}!"); // a new String, owned by this function &greeting // ❌ greeting is dropped when we return}
fn main() { let text = make_greeting("Ferris"); println!("{text}");}You’ll see a few wordings of the same error. They all mean the same thing:
cannot return reference to local variablewhen you write&greeting.cannot return value referencing local variablewhen you call a method that borrows, likegreeting.as_str()or&scores[2..].cannot return value referencing temporary valuewhen the data was never stored in a variable, liketext.to_uppercase().as_str().
Every version carries the label “returns a value referencing data owned by the current function”.
What it means
Section titled “What it means”greeting is a local variable: it’s created inside make_greeting and owned by it. When the function returns, all its local variables are dropped. The &str you return would point into a String that no longer exists.
It’s like moving out of a flat and giving your friend the key. The key is real, but the flat is now empty, or someone else lives there. The friend needs the furniture, not the key.
A common way to land here: you first wrote greeting (a String) in a function that returns &str, got E0308 (mismatched types), and the compiler suggested “consider borrowing here: &greeting”. That suggestion fixes the type but leads straight into E0515. The real fix is to change the return type.
Why Rust does this
Section titled “Why Rust does this”A reference must never outlive the value it points to. Returning a reference to a local would create a dangling reference, a pointer to freed memory. In C and C++ this compiles and then crashes (or worse, quietly returns garbage). Rust catches it at compile time.
A returned reference has to borrow from something that lives outside the function: a parameter the caller passed in, a field of self, or a 'static value like a string literal. The function’s own locals don’t qualify. This is why lifetime annotations can’t fix E0515: 'a only describes how long data lives, it can’t make a local live longer. See Ownership and Borrowing.
How to fix it
Section titled “How to fix it”Fix 1: Return the owned value
Section titled “Fix 1: Return the owned value”Choose this when the function creates the data. This is the right answer most of the time. Return String instead of &str (or Vec<T> instead of &[T]) and drop the &. The value is moved out to the caller, which then owns it:
fn make_greeting(name: &str) -> String { let greeting = format!("Hello, {name}!"); greeting // ✅ hand the whole String to the caller}
fn top_scores() -> Vec<u32> { let mut scores = vec![42, 97, 63, 88]; scores.sort(); scores.split_off(2) // the two highest, as a new Vec}
fn main() { let text = make_greeting("Ferris"); println!("{text}"); println!("{:?}", top_scores());}Output:
Hello, Ferris![88, 97]Don’t worry about cost: returning a String or Vec moves only a small pointer, length and capacity. The text itself isn’t copied. You could also simply write format!("Hello, {name}!") as the last line.
Fix 2: Take the data as a parameter and return a reference into it
Section titled “Fix 2: Take the data as a parameter and return a reference into it”Choose this when the caller already owns the data and you only want to point at part of it. If the function works on a borrowed parameter, the returned reference borrows from that parameter, which lives outside the function:
fn top_two(scores: &mut [u32]) -> &[u32] { scores.sort(); &scores[scores.len() - 2..] // ✅ points into the caller's data}
fn main() { let mut scores = vec![42, 97, 63, 88]; // owned by main let best = top_two(&mut scores); println!("{best:?}");}Output:
[88, 97]With one reference parameter, Rust fills in the lifetime for you. With two or more, you may need 'a (see E0106). This only works if you return a piece of the input. A brand-new value built from the input (to_uppercase(), format!, trim().to_string()) is a local again, so go back to Fix 1. See Slices.
Fix 3: Return &'static str for string literals
Section titled “Fix 3: Return &'static str for string literals”Choose this when every possible result is a fixed text written in your code. String literals are stored in the program itself and live forever, so returning a reference to one is always safe:
fn status_text(code: u16) -> &'static str { match code { 200 => "OK", 404 => "Not Found", 500 => "Internal Server Error", _ => "Unknown", }}
fn main() { for code in [200, 404, 418] { println!("{code}: {}", status_text(code)); }}Output:
200: OK404: Not Found418: UnknownDon’t try to “fix” a format! result by writing &'static str in the signature. You’ll just get E0515 again, because the String is still local.
Fix 4: Use Cow when you sometimes borrow and sometimes create
Section titled “Fix 4: Use Cow when you sometimes borrow and sometimes create”Choose this when the function usually returns its input unchanged, but sometimes has to build a new string. Cow (clone-on-write) holds either a borrowed &str or an owned String, so you only allocate when you must:
use std::borrow::Cow;
fn safe_file_name(name: &str) -> Cow<'_, str> { if name.contains(' ') { Cow::Owned(name.replace(' ', "_")) // new String only when needed } else { Cow::Borrowed(name) // no copy at all }}
fn main() { for name in ["report.txt", "my notes.txt"] { let clean = safe_file_name(name); let kind = match clean { Cow::Borrowed(_) => "borrowed", Cow::Owned(_) => "owned", }; println!("{clean} ({kind})"); }}Output:
report.txt (borrowed)my_notes.txt (owned)Cow is an optimization. If you’re not sure you need it, return a String (Fix 1). More in Weak & Cow.
Where you’ll usually see it
Section titled “Where you’ll usually see it”Returning s.as_str() of a local String. greeting.as_str() borrows greeting, so it fails the same way as &greeting, with the wording “cannot return value referencing local variable”. Fix: return greeting and make the return type String.
A .map() that creates new strings. This looks harmless, but each to_lowercase() makes a temporary String that is dropped right after .as_str():
fn tags(input: &str) -> Vec<&str> { input.split(',').map(|t| t.trim().to_lowercase().as_str()).collect() // ❌ cannot return value referencing temporary value}Fix: return Vec<String> and drop .as_str(): .map(|t| t.trim().to_lowercase()).collect(). (Without to_lowercase(), t.trim() is a slice of input, so Vec<&str> would work. See Iterators.)
A method that formats a field. fn full_name(&self) -> &str { &format!("{} {}", self.first, self.last) } gives “cannot return reference to temporary value”. Fix: -> String. Returning &self.first is fine, because that data lives in the struct, not in the method. See Structs.
A function with no reference parameters. fn make_greeting() -> &String fails earlier, with E0106, because there’s nothing it could borrow from. The fix is the same: return String.
Related errors
Section titled “Related errors”- E0106: missing lifetime specifier: often appears first; adding
'awon’t fix a reference to a local. - E0597: borrowed value does not live long enough: the same problem inside one function, with a block or loop instead of a return.
- E0308: mismatched types: returning
Stringwhere&stris declared. Change the return type, don’t add&. - E0382: borrow of moved value: after switching to owned values, watch out for using a value after you moved it.
E0716: temporary value dropped while borrowed: a reference to a temporary that is used later in the same function.- All compiler errors explained
Quick check
Section titled “Quick check”Quick check
1. A function builds a message with format! and returns &message. What's usually the right fix?
2. Which function compiles?
3. When is Cow<str> a good return type?
Score: 0 / 3
Read more
Section titled “Read more”- E0515 — Rust error codes index: the official explanation of this error.
- Dangling References — The Rust Book: the
dangleexample and why the answer is to return theStringdirectly. - Validating References with Lifetimes — The Rust Book: why a returned reference must be tied to one of the parameters.
- The Slice Type — The Rust Book: returning
&strslices that borrow from the input, the safe version of Fix 2. Cow— standard library docs: the clone-on-write type from Fix 4, with examples.