References & Borrowing
Moving a value every time you use it would make Rust unbearable — references let you borrow access without taking ownership, compiler-enforced for free.
The last lesson ended on a real problem: if passing a String into a function moves it, and the caller loses access afterward, then every function that merely wants to read a value would force you to either give it up permanently or .clone() it defensively. Neither is acceptable for code you write constantly — cloning everywhere hides real costs, and losing access to your own variables just to print them would make Rust unbearable. The fix is a reference: writing &s1 lets a function borrow access to the value s1 owns without taking ownership of it at all.
A reference is written &T for an immutable, read-only borrow. A function signature like fn calculate_length(s: &String) -> usize is explicit about its contract: it receives access to a String it does not own, so it can read from it — call .len(), iterate its characters — but it cannot modify it and, crucially, it does not drop it when the function ends, because it was never the owner. After the call returns, the original variable in the caller is exactly as valid and usable as before, because nothing was ever moved.
When a function does need to modify borrowed data, you reach for &mut T instead. The variable being borrowed must itself be declared mut, and you pass &mut s rather than &s; inside the function, an &mut String parameter lets you call mutating methods like .push_str() directly on it. The signature fn add_exclamation(s: &mut String) tells every caller, just by reading it, that this function is going to change the value they pass in — no need to read the implementation to know that.
Rust enforces exactly one rule about how many references can exist at once, and it's worth memorizing because the compiler will hold you to it constantly: at any given point, you may have any number of immutable references (&T) to a value, or exactly one mutable reference (&mut T) — never both kinds at the same time. This isn't an arbitrary restriction; it's the precise condition that makes a data race possible in the first place. A data race requires two or more pointers to the same memory where at least one writes and there's no synchronization between them — by making that exact configuration a compile error, Rust turns a bug that traditionally only shows up under concurrent load, non-deterministically, into something you can't even ship.
The same borrow checker also refuses to let a reference outlive the data it points to, which eliminates dangling references entirely. In C, it's easy to return a pointer to a local variable and get a pointer to freed stack memory the moment the function returns — undefined behavior that might work fine in testing and crash in production. Rust's compiler tracks how long every reference is allowed to remain valid (its "lifetime") and rejects any function that would let a reference escape past the scope of the data it borrows from — you'll meet explicit lifetime syntax later, but the underlying guarantee is already protecting you here, silently, in every borrow you write.
You'll meet this rule most concretely as a compiler error the first time you try to mix a mutable and an immutable borrow while both are still "in use." Modern Rust is smart about what "in use" means — thanks to non-lexical lifetimes, a borrow's active lifetime ends at its last actual use, not at the end of its enclosing block — so two immutable references, r1 and r2, can exist, be printed, and then a new mutable reference r3 can be created afterward in the very same scope, because r1 and r2 are no longer read past that point. Try to create r3 while r1 or r2 is still going to be used later, though, and you get error[E0502]: cannot borrow s as mutable because it is also borrowed as immutable — and the fix is almost always to finish using the existing borrows before starting a new, conflicting one, or to shrink the borrows into smaller, non-overlapping scopes.
Put together, references are how idiomatic Rust passes data around without constantly moving or cloning it, and a function's signature becomes a complete, honest contract: &T means "I'll read this," &mut T means "I'll change this," and a plain T means "I'm taking this from you." Once that contract is visible in every signature you write and read, a huge amount of what used to require careful documentation in other languages becomes something the compiler simply verifies for you.
fn calculate_length(s: &String) -> usize {s.len()}fn main() {let s1 = String::from("hello");let len = calculate_length(&s1);println!("{} has length {}.", s1, len);}
calculate_length borrows s1 through an immutable reference instead of taking ownership, so s1 is still perfectly valid and usable in the println! after the function call returns.
fn add_exclamation(s: &mut String) {s.push_str("!");}fn main() {let mut greeting = String::from("Hello");add_exclamation(&mut greeting);println!("{}", greeting);}
Passing &mut greeting lets add_exclamation modify the caller's String in place through the reference, which is only possible because greeting itself was declared mut.
fn main() {let mut s = String::from("hello");let r1 = &s;let r2 = &s;println!("{} and {}", r1, r2);// The following would NOT compile if placed here, because r1 and r2// are still considered active through the println! call above:// let r3 = &mut s;// println!("{}", r3);// error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable// Fix: since r1 and r2 are not used again after the line above, Rust's// borrow checker (using non-lexical lifetimes) allows a new mutable// borrow to start here:let r3 = &mut s;r3.push_str(", world");println!("{}", r3);}
Two immutable references coexist safely and are printed, and thanks to non-lexical lifetimes a new mutable reference is allowed right after because r1 and r2 are never read again — the commented-out block shows the E0502 error you'd get if you tried to create that mutable reference any earlier, while the immutable ones were still going to be used.
🧠 Check your understanding
0/1 · 0/1 answered1. Why does Rust forbid holding a mutable reference and one or more immutable references to the same value at the same time?