References and Borrowing
Returning References
A function can return a reference (-> &T, -> &mut T, -> &str, -> &[T]) to allow the caller to inspect or modify existing data without copying it. However, because a function's stack frame is destroyed the instant it returns, every returned reference must point to memory that outlives the function call.
Valid Sources for a Returned Reference
A function is only allowed to return a reference that comes from one of two places:
| Valid Source | Example Signature | Why It Is Memory-Safe |
|---|---|---|
| 1. Borrowed from an Input Parameter | fn first_element(s: &[i32]) -> Option<&i32> |
The data is owned by the caller (outside the function) and remains alive after the function returns |
2. Borrowed from 'static Memory |
fn label(ok: bool) -> &'static str |
String literals ("ok") and static items live in the binary's data segment for the entire program run |
| ❌ Local Variable Owned by the Function | fn bad() -> &str { let s = String::new(); &s } |
Forbidden (E0515) — s is dropped when the function exits, so &s would immediately dangle! |
Safe Return (Points back into Caller's Frame / Heap):
Caller (`main`) Callee (`first_non_empty`)
+-------------------------+ +-----------------------------------+
| lines: [String; 2] | <───┐ | fn first_non_empty(s: &[String]) |
| chosen: &str ───────────+─────┴─────+─── Returns sub-slice of `s`! |
+-------------------------+ +-----------------------------------+
// 1. Returning a reference borrowed from an input parameter (`slice`):
fn find_above_threshold(slice: &[i32], threshold: i32) -> Option<&i32> {
slice.iter().find(|&&x| x > threshold)
}
// 2. Returning a mutable reference into an input parameter (`slice: &mut [i32]`):
fn first_mut_element(slice: &mut [i32]) -> Option<&mut i32> {
slice.first_mut()
}
// 3. Returning a `'static` reference to string literals:
fn status_name(code: u16) -> &'static str {
match code {
200 => "OK",
404 => "Not Found",
_ => "Other",
}
}
fn main() {
let mut numbers = [4, 9, 15, 22];
if let Some(found) = find_above_threshold(&numbers, 10) {
println!("Found > 10: {found}");
}
if let Some(first) = first_mut_element(&mut numbers) {
*first = 100; // Mutates `numbers[0]` in place through the returned `&mut i32`!
}
println!("Updated numbers: {numbers:?}, status 200 = {}", status_name(200));
}
Found > 10: 15
Updated numbers: [100, 9, 15, 22], status 200 = OK
How a Returned Reference Extends the Input Borrow
When a function fn get_part(data: &Data) -> &Part returns a reference derived from data, the borrow of data remains active in the caller for as long as the returned &Part reference is kept alive!
- If the function took
&dataand returned&Part,datastays immutably borrowed (frozen) until the caller finishes using&Part. - If the function took
&mut dataand returned&mut Part(or even an immutable&Part!),datastays exclusively borrowed until the caller finishes using the returned reference!
Common Error: Returning a Reference to a Local Variable (E0515)
If a function constructs a new String, Vec<T>, or struct locally, it must return the owned value (-> String), never a reference (-> &str):
// ❌ does not compile
fn format_greeting(name: &str) -> &str {
let text = format!("Hello, {name}!");
&text
}
fn main() {}
error[E0515]: cannot return value referencing local variable `text`
--> src/main.rs:3:5
|
3 | &text
| ^^^^^ returns a value referencing data owned by the current function
|
= help: use `.to_owned()` or return an owned `String` instead
error: aborting due to 1 previous error