Skip to content

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.)

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 function

This 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 variable when you write &greeting.
  • cannot return value referencing local variable when you call a method that borrows, like greeting.as_str() or &scores[2..].
  • cannot return value referencing temporary value when the data was never stored in a variable, like text.to_uppercase().as_str().

Every version carries the label “returns a value referencing data owned by the current function”.

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.

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.

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]
▶ Try it Yourself

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]
▶ Try it Yourself

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: OK
404: Not Found
418: Unknown
▶ Try it Yourself

Don’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)
▶ Try it Yourself

Cow is an optimization. If you’re not sure you need it, return a String (Fix 1). More in Weak & Cow.

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.

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