Borrowing & References
Borrowing lets code use data without taking ownership. Shared references (&T) allow concurrent readers. Mutable references (&mut T) grant exclusive write access.
Recipe
fn push_char(s: &mut String, c: char) {
s.push(c);
}
fn display(s: &str) {
println!("{s}");
}
fn main() {
let mut msg = String::from("hi");
display(&msg);
push_char(&mut msg, '!');
display(&msg);
}When to reach for this: Read-mostly APIs with &T, builders and mutation with &mut T, avoiding ownership transfer.
Working Example
fn split_at_mut<T>(slice: &mut [T], mid: usize) -> (&mut [T], &mut [T]) {
slice.split_at_mut(mid)
}
fn main() {
let mut data = vec![1, 2, 3, 4];
let (left, right) = split_at_mut(&mut data, 2);
left[0] = 10;
right[0] = 40;
println!("{:?}", data);
}What this demonstrates:
- Exclusive
&mutfor mutation split_at_mutcreates two non-overlapping&mutslices safely- Borrow ends when
left/rightgo out of scope &str/&[T]follow same rules
Deep Dive
Aliasing Rules
- Any number of
&Tor exactly one&mut T - References must always be valid (no dangling)
- Reference scope ends at last use (NLL) - see dedicated page
Reborrowing
let mut s = String::from("a");
let r1 = &mut s;
let r2 = &*r1; // reborrow shared while mutable loan active in subset
println!("{r2}");& Coercion
&String -> &str, &Vec<T> -> &[T] via Deref.
Gotchas
- Two
&mutto same variable - Compile error. Fix: Scope splits,split_at_mut, or interior mutability. - Read while
&mutactive - Forbidden. Fix: End mutable borrow before shared read. - Returning
&to local data - Dangling reference. Fix: Return owned value or tie lifetime to input. - Mutable borrow across function calls holding
&mut- Extends loan. Fix: Limit borrow scope with blocks. - Iterating with
&mutcollection while pushing - Iterator invalidation. Fix: Collect indices first or usedrain.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Owned parameter | Caller transfers value | Only reading or short mutation |
RefCell/Mutex | Shared mutation needed | Exclusive &mut works |
| Index + raw access | Unsafe hot paths | Safe abstractions suffice |
| Channels | Cross-thread handoff | Same-thread borrowing works |
FAQs
Can I have multiple `&mut` to different fields?
Yes, with disjoint field borrows: fn f(a: &mut S, b: &mut T) on different fields via split borrows.
What is a dangling reference?
A reference pointing to freed or moved data. Prevented by lifetimes and borrow checker.
Does `&mut` allow reading?
Yes. &mut is exclusive read/write access.
Can references be stored in structs?
Yes with lifetime parameters: struct Holder<'a> { r: &'a str }.
What about `&'static`?
Borrow valid for entire program - string literals, leaked allocations, static data.
How do borrows end?
At end of scope or last use (non-lexical lifetimes).
Can closures borrow?
Yes. Closures capture by reference (&T, &mut T) or move.
Are `&T` and `*const T` the same?
No. References are safe, proven valid. Raw pointers need unsafe to dereference.
Borrowing in match arms?
Bindings in match can be refs: match &mut v { ... }.
How does borrowing interact with `async`?
Held &mut across .await is often rejected. Use owned state or Mutex in async.
Related
- The Borrow Checker - compiler reasoning
- Lifetimes Explained - why
'aexists - Non-Lexical Lifetimes & Reborrows - modern behavior
- References & Slices - slice basics
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.