Mutable Borrows
Mutating Through a Reference
Section titled “Mutating Through a Reference”A shared reference &T is read-only. When you need to modify a borrowed value, use a mutable reference: &mut T.
Two conditions must both be true:
- The original variable must be declared with
mut. - The reference itself must be written as
&mut.
fn append_world(s: &mut String) { s.push_str(", world");}
fn main() { let mut greeting = String::from("hello"); append_world(&mut greeting); println!("{}", greeting); // hello, world}The Exclusive-Access Rule
Section titled “The Exclusive-Access Rule”The borrow checker enforces a single invariant:
At any given point in the code, you may have either any number of shared references or exactly one mutable reference to a value — never both at the same time.
This rule prevents data races at compile time. A data race occurs when two or more threads access the same memory concurrently and at least one access is a write. By making simultaneous mutable access a compile error, Rust eliminates the entire class of data-race bugs — even in single-threaded code the same rule keeps logic sound.
// THIS DOES NOT COMPILE — shown for teaching only://// fn main() {// let mut s = String::from("hello");// let r1 = &s; // shared borrow// let r2 = &mut s; // error[E0502]: cannot borrow `s` as mutable// // because it is also borrowed as immutable// println!("{} {}", r1, r2);// }Non-Lexical Lifetimes (NLL)
Section titled “Non-Lexical Lifetimes (NLL)”Since Rust 2018, the compiler uses Non-Lexical Lifetimes (NLL): a borrow ends at the last point it is actually used, not at the end of the enclosing block. This means you can safely start a new borrow after the previous one’s last use, even within the same scope.
fn main() { let mut s = String::from("hello");
let r1 = &mut s; r1.push_str(", world"); // last use of r1 // r1's borrow ends here (NLL)
let r2 = &s; // perfectly fine — r1 is gone println!("{}", r2);}Mutable References and Function Parameters
Section titled “Mutable References and Function Parameters”Passing &mut T to a function signals that the function may modify the value. This makes mutation explicit and visible at the call site — you can see &mut at every point a value might change.
fn double(n: &mut i32) { *n *= 2; // dereference to modify the value behind the reference}
fn main() { let mut value = 7; double(&mut value); println!("{}", value); // 14}Note the *n dereference operator: to modify the value a mutable reference points to, you must dereference it first. For String methods like .push_str(), Rust applies the dereference automatically through a feature called auto-deref.
fn append_world(s: &mut String) { s.push_str(", world");}
fn main() { let mut greeting = String::from("hello"); println!("before: {}", greeting); append_world(&mut greeting); println!("after: {}", greeting);
// One mutable borrow at a time let r1 = &mut greeting; r1.push_str("!"); println!("mutated: {}", r1); // r1 is no longer used here (NLL ends the borrow)
let r2 = &greeting; // shared borrow is fine now println!("shared: {}", r2);}Compiling…