Rust in Depth

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.

Also called
aliasing xor mutabilityshared XOR mutableone mutable or many shared別名與可變互斥共享或可變,二擇一