The Error
error[E0515]: cannot return value referencing local variable `result`
--> src/main.rs:5:5
|
5 | &result
| ^------
| ||
| |`result` is borrowed here
| returns a value referencing data owned by the current function
This error means you tried to return a reference to something the function itself owns. The moment the function returns, that local variable is dropped โ so any reference to it would immediately point to freed memory. Rust's borrow checker catches this before your code ever runs.
Root Cause
A local variable dies when its function returns. That's it. Rust's rule is simple: a reference can't outlive the data it points to. Return a reference to a local, and you'd get a dangling pointer โ the kind of bug that crashes C programs and corrupts memory silently. Rust just refuses to compile it.
The classic trigger looks like this:
fn get_greeting(name: &str) -> &str {
let result = format!("Hello, {}!", name); // result is a local String
&result // ERROR: result is dropped here, reference would dangle
}
The compiler error:
error[E0515]: cannot return value referencing local variable `result`
--> src/main.rs:3:5
|
3 | &result
| ^------
| |`result` is borrowed here
| returns a value referencing data owned by the current function
Step-by-Step Fixes
Fix 1: Return the Owned Type Instead of a Reference
If the function creates data, just return it. No reference needed.
// Before (broken)
fn get_greeting(name: &str) -> &str {
let result = format!("Hello, {}!", name);
&result // E0515
}
// After (fixed)
fn get_greeting(name: &str) -> String {
format!("Hello, {}!", name) // Return the owned String directly
}
Same pattern applies to collections:
// Before (broken)
fn even_numbers(input: &[i32]) -> &[i32] {
let result: Vec<i32> = input.iter().filter(|&&x| x % 2 == 0).cloned().collect();
&result // E0515
}
// After (fixed)
fn even_numbers(input: &[i32]) -> Vec<i32> {
input.iter().filter(|&&x| x % 2 == 0).cloned().collect()
}
Fix 2: Return a Reference to the Input Parameter
Not building new data? Just selecting or slicing from the input? Reference the input directly โ no local copy needed.
// Before (broken โ creates local copy unnecessarily)
fn first_word(s: &str) -> &str {
let owned = s.to_string(); // unnecessary local variable
owned.split_whitespace().next().unwrap_or(&owned) // E0515
}
// After (fixed โ reference the input directly)
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
// lifetime of return value tied to `s`, which is fine
}
Fix 3: Use Cow<'_, str> When You Need Both
Some functions conditionally return either a borrowed value or a freshly created one. Cow (Clone-on-Write) is built exactly for this case:
use std::borrow::Cow;
fn sanitize(input: &str) -> Cow<'_, str> {
if input.contains('<') {
Cow::Owned(input.replace('<', "<")) // newly created String
} else {
Cow::Borrowed(input) // just borrow the original
}
}
fn main() {
let clean = sanitize("hello <world>");
println!("{}", clean); // hello <world>
}
Fix 4: Restructure to Avoid Building Data Inside the Function
Sometimes the real fix is deleting unnecessary allocation. A function that builds a local String just to slice it can often work directly on the &str input instead:
// Before (broken)
fn get_domain(url: &str) -> &str {
let trimmed = url.trim_start_matches("https://").to_string(); // local String
trimmed.split('/').next().unwrap_or("") // E0515
}
// After โ trim directly on the &str input, no local String needed
fn get_domain(url: &str) -> &str {
url.trim_start_matches("https://")
.trim_start_matches("http://")
.split('/')
.next()
.unwrap_or("")
}
Verify the Fix
Run cargo build. If E0515 is gone, you're done. Also run cargo test to confirm nothing broke:
cargo build
cargo test
For a faster iteration loop, cargo check skips code generation entirely and catches type and lifetime errors in roughly half the time of a full build:
cargo check
Quick Decision Guide
- Building new data inside the function? โ Return the owned type (
String,Vec<T>, etc.) - Just selecting or slicing from the input? โ Return a reference to the input parameter
- Sometimes building, sometimes not? โ Use
Cow<'_, str>orCow<'_, [T]> - Caller needs to mutate the result? โ Always return the owned type
Common Mistakes
Adding lifetime annotations does not fix this. It's a very common attempt:
// Still broken โ lifetime annotation doesn't extend variable lifetime
fn get_greeting<'a>(name: &'a str) -> &'a str {
let result = format!("Hello, {}!", name);
&result // E0515 โ the annotation can't save a local variable
}
Lifetime annotations describe relationships between existing lifetimes. They don't create new ones or extend a local variable's scope. The data has to physically outlive the reference โ no annotation changes that.

