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 &data and returned &Part, data stays immutably borrowed (frozen) until the caller finishes using &Part.
  • If the function took &mut data and returned &mut Part (or even an immutable &Part!), data stays 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