Rust Aliasing Rules
Two pointers alias when they point at overlapping memory. The aliasing rules are what let the compiler make code faster.
Two names for one value
These two pointers alias — they reach the same memory:
let x = 5;
let a = &x;
let b = &x; // a and b alias: both point at xWhy it matters: the compiler caches
A value behind a & cannot change while the reference lives. So the compiler may read *input once, keep the copy, and trust it for both tests:
fn compute(input: &u32, output: &mut u32) {
if *input > 10 { *output = 1; }
if *input > 5 { *output *= 2; }
}The two rules
Everything rests on two promises. Every optimisation the compiler makes with references assumes them:
// While a &T is alive, the value behind it does not change.
// While a &mut T is alive, only that &mut touches the value.A &mut shuts everyone else out
While the one mutable borrow lives, no second borrow of the value may be used at all. That exclusivity is Rule 2 at work:
let only = &mut value;
*only += 1; // while `only` lives, nothing else touches valueThe one sanctioned exception: UnsafeCell
UnsafeCell turns the first rule off for its contents — "this value may change behind a shared reference". Cell, RefCell and Mutex are all built on it:
use std::cell::UnsafeCell;
let cell = UnsafeCell::new(5);Example
fn compute(input: &u32, output: &mut u32) {
if *input > 10 {
*output = 1;
}
if *input > 5 {
*output *= 2;
}
}
fn main() {
let input = 20;
let mut output = 0;
compute(&input, &mut output);
println!("output = {output}");
}Key points
- Breaking a rule is undefined behaviour.
- This holds even when the program prints the right answer once.
- Another build, machine or small change can expose it
- Raw pointers have no aliasing promises of their own.
- But any reference you create from one must obey the rules.
- It must obey them while it is alive
- Aliasing is the quiet reason safe Rust is fast.
- The borrow rules are safety rules.
- This idea has been at work since the References lesson, without its name.
- Aliasing happens when two pointers reach the same memory.
In this example, compute receives input as a &u32 and output as a &mut u32. The borrow rules promise these can never be the same value: a &mut is exclusive, so output cannot alias input. That promise lets the compiler read *input once and trust the copy. 20 is greater than 10, so output becomes 1. The same trusted 20 is greater than 5. So output doubles to 2. If output could secretly be input, the first write would change the second test. That input is impossible in Rust. The compiler is allowed to rely on its impossibility.
Note
They are also optimisation rules.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
A program breaks the aliasing rules but prints the expected answer on your machine. Is it correct?