Step 6 of 7
When to return owned data instead
Some lifetime errors have no lifetime fix at all. The most common one:
fn make_greeting(name: &str) -> &str {
let greeting = format!("Hello, {name}!");
&greeting
}
fn main() {
println!("{}", make_greeting("Ferris"));
}
error[E0515]: cannot return reference to local variable `greeting`
--> src/main.rs:3:5
|
3 | &greeting
| ^^^^^^^^^ returns a reference to data owned by the current function
The signature is fine as far as elision goes (one input, so rule 2 says the result borrows from name). The body breaks that promise: greeting is a new String owned by make_greeting, and it's dropped when the function returns. Any reference to it would dangle. Writing <'a> everywhere changes nothing, and neither does 'static: the data simply doesn't exist after the function ends. (Returning greeting.as_str() gives the same error in another wording: "returns a value referencing data owned by the current function".)
The fix is to return the owned value: give ownership of the String to the caller.
fn make_greeting(name: &str) -> String {
format!("Hello, {name}!")
}
fn first_name(full: &str) -> &str {
full.split(' ').next().unwrap_or(full)
}
fn main() {
let full = String::from("Ana Lovelace");
println!("{}", make_greeting(first_name(&full)));
}
Hello, Ana!
Moving a String out of a function is cheap: only the pointer, length and capacity are copied, never the text.
Borrow or own? A rule of thumb
- The function builds new data (
format!,to_uppercase,replace,join, collecting into aVec): return the owned type,StringorVec<T>. - The function selects part of its input (
trim, slicing,split_once, finding an item): return a reference into the input, likefirst_name. - A struct that's stored (in a collection, a cache, app state), returned through several layers or sent to another thread: give it owned fields. Use borrowing structs (
Tokenizer<'a>) for short-lived views. - If lifetimes start spreading through half your code to save copying a few small strings, owning is fine. A
clone()of a shortStringcosts nanoseconds; a week fighting'as costs a week. Profile first, optimize where it matters (the performance module showsCow<str>for "borrowed when possible, owned when needed").
Experienced Rust developers return owned data far more often than beginners expect. It's not giving up; it's choosing the simplest correct design.
Common mistakes
- Returning a reference to a local variable or a temporary.
&format!(...),&swhereswas made in the function,vec.iter().collect::<Vec<_>>().as_slice(): all E0515. Return the owned value. - Reaching for
'static. As the previous step showed, it doesn't apply to data created at run time. - Over-cloning the other way. Taking
name: Stringwhen you only read it forces callers to give up or copy their string. Take&strin, returnStringout when you create something new.
Your task
Fix the three functions. make_greeting and initials build new strings, so they must return owned data. last_name only selects part of its input: keep it returning a &str. Results: make_greeting("Ana") is Hello, Ana!, initials("Ada Lovelace") is A.L., last_name("Ada Lovelace") is Lovelace.
Challenge 2
Report borrows its title, but build_report creates the title with format!, so there's nothing long-lived to borrow from. Change Report to own its title (and drop the lifetime parameter), so build_report("sales", &[3, 4]) returns a report with title Report: sales and total 7.
Challenge 3
Write two functions over a list of words:
fn shout_all(words: &[&str]) -> Vec<String>returns every word in upper case with!added ("hi"becomes"HI!"). These are new strings: return owned data.fn longest_word<'a>(words: &[&'a str]) -> Option<&'a str>returns the longest word (the first one on a tie), orNonefor an empty list. It returns one of the words themselves, borrowed from the original text.
Notice the signature of longest_word: the result is tied to the words (&'a str), not to the slice holding them. A hidden test throws the slice away and keeps the word.
Previous: 'static: references that live forever Next: Reading lifetime errors