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 Stringand&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 Stringor&mut Vec<T>, because a slice&mut [T]or&mut strhas 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 withE0499), guaranteeing insidetransferthatfromandtoare two distinct accounts!