Borrowing and references in Rust (&T and &mut T)
How borrowing works in Rust: shared references &T, mutable references &mut T, the one writer or many readers rule, and dereferencing with *.
Moving a value into every function that needs to look at it would be painful. Borrowing lets code use a value without taking ownership: you hand out a reference, and the owner keeps the value.
&valuecreates a shared reference (&T): read-only access.&mut valuecreates a mutable reference (&mut T): read and write access. The variable itself must be declaredmut.
fn count_words(text: &str) -> usize {
text.split_whitespace().count()
}
fn add_signature(text: &mut String) {
text.push_str(" -- Ferris");
}
fn main() {
let mut note = String::from("ship it on friday");
println!("{} words", count_words(¬e));
add_signature(&mut note);
println!("{note}");
}
4 words
ship it on friday -- Ferris
note is never moved, so main can keep using it.
The borrowing rules
At any moment, a value can have either:
- any number of shared references
&T, or - exactly one mutable reference
&mut T,
but not both. And a reference can never outlive the value it points to.
This "many readers or one writer" rule is checked at compile time by the borrow checker. It's what prevents a whole family of bugs:
- Iterator invalidation: pushing to a
Veccan move its elements to a new heap block. Any reference into the old block would dangle. The rule stops you from pushing while a reference into theVecis alive. - Data races: two threads writing the same memory at once can't happen if only one
&mutexists. - Dangling pointers: references to freed memory are rejected.
Borrows end at their last use
A borrow lasts from where it's created to the last place it's used, not to the end of the block (this is called non-lexical lifetimes). This compiles, because first isn't used after the push:
fn main() {
let mut scores = vec![10, 20];
let first = &scores[0];
println!("first {first}");
scores.push(30);
println!("{scores:?}");
}
first 10
[10, 20, 30]
Move the println! of first below the push and you get error E0502.
Dereferencing with *
To read or change the value behind a reference, use *: *count += 1. Method calls and field access dereference automatically (text.len(), player.score), and so does println!, so you mostly need * for assignment and arithmetic on numbers.
The errors you'll meet
| Error | Meaning |
|---|---|
| E0502 | mutable borrow while a shared borrow is still in use (or the reverse) |
| E0499 | two mutable borrows alive at once |
| E0596 | &mut of something not declared mut, or behind a & |
| E0597 | the value is dropped while a reference to it is still used |
| E0506 | assigning to a value while it's borrowed |
| E0515 | returning a reference to a local variable |
Each one is the compiler finding a real bug pattern, not being pedantic.
Best practices
- Accept
&strand&[T]in functions that only read; accept&mut Twhen you modify the caller's value; take ownership only to store or consume. - Keep borrows short: copy small values out (
let first = v[0];) instead of holding a reference across a mutation. - When the borrow checker fights you, the usual fixes are reordering statements, splitting a function in two, or using indices instead of references, not
.clone()everywhere.
Example
fn total(prices: &[u32]) -> u32 {
let mut sum = 0;
for p in prices {
sum += p;
}
sum
}
fn apply_discount(prices: &mut [u32], percent: u32) {
for p in prices.iter_mut() {
*p -= *p * percent / 100;
}
}
fn bump(counter: &mut u32) {
*counter += 1;
}
fn main() {
let mut cart = vec![100, 250, 40];
let before = total(&cart);
apply_discount(&mut cart, 10);
let after = total(&cart);
println!("{cart:?}");
println!("before {before}, after {after}");
let mut visits = 0;
bump(&mut visits);
bump(&mut visits);
println!("visits {visits}");
let r1 = &cart;
let r2 = &cart;
println!("two readers: {} {}", r1.len(), r2[0]);
}
Output:
[90, 225, 36]
before 390, after 351
visits 2
two readers: 3 90