References and Borrowing

References as Function Parameters

Passing references as function parameters (fn analyze(data: &T) or fn update(data: &mut T)) allows a function to read or modify a caller's values in place without taking ownership of them and without copying large buffers on the stack or heap.

Choosing Between &T, &mut T, and T in Function Signatures

Parameter Type Access Granted to Function Effect on Caller's Variable Idiomatic Slice Form
&T (Shared reference) Read-only access Remains owned and readable by caller Prefer &str over &String, and &[U] over &Vec<U>
&mut T (Mutable reference) Read and in-place write access Remains owned by caller; holds updated value after call Use &mut String / &mut Vec<U> (if growing length) or &mut [U] (if only modifying elements)
T (Owned value) Full ownership (can store, move, or drop) Moved into function (unless T: Copy) String, Vec<U>
// 1. Read-only slice reference `&[i32]`:
fn min_and_max(values: &[i32]) -> Option<(i32, i32)> {
    let first = *values.first()?;
    let mut min = first;
    let mut max = first;
    for &v in &values[1..] {
        if v < min { min = v; }
        if v > max { max = v; }
    }
    Some((min, max))
}

// 2. Mutable reference `&mut Vec<i32>` (needed because `.push()` grows the vector's length):
fn append_range(target: &mut Vec<i32>, start: i32, end: i32) {
    for v in start..=end {
        target.push(v);
    }
}

fn main() {
    let mut readings = vec![15, 42];
    append_range(&mut readings, 1, 3);

    let bounds = min_and_max(&readings);
    println!("Readings: {readings:?} -> min/max = {bounds:?}");
}
Readings: [15, 42, 1, 2, 3] -> min/max = Some((1, 42))

Why &String and &Vec<T> Are Antipatterns in Read-Only Parameters

When a function only needs to read text or a sequence of elements, always declare the parameter as &str or &[T] rather than &String or &Vec<T>:

fn bad_signature(s: &String, v: &Vec<i32>)
  - Rejects string literals `"hello"` (`&'static str`)!
  - Rejects stack arrays `&[1, 2, 3]` (`&[i32; 3]`) and sub-slices `&v[1..3]`!
  - Adds unnecessary double-pointer indirection (`&Vec` -> `Vec` -> `[i32]`).

fn good_signature(s: &str, v: &[i32])
  - Accepts `&String`, `"literal"`, and `&s[0..4]` via automatic deref coercion!
  - Accepts `&Vec<i32>`, `&[1, 2, 3]`, and `&v[1..3]`!

Exception for &mut String and &mut Vec<T>: If a function needs to push, insert, or clear characters or elements (changing the length/capacity of the buffer), it must accept &mut String or &mut Vec<T>, because a slice &mut [T] or &mut str has a fixed length and cannot reallocate.

Multiple Reference Parameters and Aliasing Rules at Call Sites

Because Rust enforces Aliasing XOR Mutability across function parameters:

  • A function fn compare(a: &i32, b: &i32) can be called with the same variable twice: compare(&x, &x).
  • A function fn transfer(from: &mut Account, to: &mut Account) cannot be called with the same variable twice (transfer(&mut acc, &mut acc) fails with E0499), guaranteeing inside transfer that from and to are two distinct accounts!