the &T XOR &mut T discipline
Most of the nastiest bugs in C and C++ come from one situation: two pieces of code reach the same memory at the same time, and at least one of them is writing. A pointer used while another path resizes the buffer it points into; an iterator that dangles because the collection underneath it was mutated; two threads racing on one variable. Rust's central safety rule attacks all of these at once with a single discipline: at any moment, a value may have many shared references (&T) OR exactly one mutable reference (&mut T), but never both. Sharing and mutation are mutually exclusive — that is the 'XOR'.
Mechanically the borrow checker enforces it as a counting rule over each value: you can hand out any number of &T (read-only borrows) at once, because many readers never interfere; or you can hand out a single &mut T (a read-write borrow), which is exclusive — while it is live, no other reference of any kind to that value may exist, not even a shared one. The two cannot overlap. A &mut T is therefore a promise of exclusive access: as long as you hold it, nothing else can observe or change the value behind your back. This is exactly the guarantee a C programmer wishes the restrict qualifier gave them everywhere, except Rust makes it the default and checks it. The rule is what makes data races impossible in safe Rust: a data race needs two threads, one of them writing, touching shared data — but a write needs &mut T, which by definition cannot coexist with any other access.
Why this matters: aliasing XOR mutability is THE soundness foundation; lifetimes, Send/Sync, and the absence of data races all rest on it. It is also the rule beginners fight most, because it forbids patterns that feel innocent — handing a mutable reference to a function while still reading the value elsewhere, or mutating a list while iterating it. The honest point is that this is not Rust being fussy; it is Rust refusing the exact aliasing that causes the silent corruption. When you genuinely need shared mutation (a graph, a shared counter), you do not break the rule — you move the check to run time with interior mutability (Cell, RefCell, Mutex), which is the next entry's job.
let mut v = vec![1, 2, 3]; let a = &v; // shared borrow let b = &v; // ANOTHER shared borrow: fine, both read-only println!("{a:?} {b:?}"); let m = &mut v; // exclusive borrow // let c = &v; // error[E0502]: cannot also borrow `v` as shared m.push(4); // only `m` may touch v while it is live
Many readers, or one writer — never both at once. This single rule is why safe Rust cannot have a data race.
This discipline holds in SAFE Rust. Inside unsafe, with raw pointers, you can create two mutable aliases to the same data — and doing so when you should not is undefined behaviour, which is exactly what Stacked Borrows and Miri exist to catch.