Recipe
Review an unsafe block
Use when: a change adds or edits an unsafe block, unsafe fn or unsafe impl
Goal: each unsafe operation is necessary, documented, and valid for every input the safe code around it allows
Taught in Checking unsafe code
Quick recipe
-
1
Look for a safe standard-library function that does the same job (
get_disjoint_mut,split_at_mut,to_bits, an atomic).Example
A
static mutcounter: replace it with anAtomicU32, and the review is over.Why: the safest unsafe block is the one that isn't there.
-
2
For every dereference, unsafe call or impl in the block, read the documented conditions.
Example
&mut *p.add(i):p.add(i)in bounds; the&mutvalid, aligned, and not overlapping any other live reference.Why: the SAFETY comment can only be checked against a complete list.
-
3
Every condition must be backed by a fact in the code: a check, a loop bound, a private invariant.
Example
"i and j are in bounds (checked above)", but nothing checks
i != j: the review has found the missing condition.Why: a comment that names no checkable fact is a hope.
-
4
Ask what safe arguments could reach the block: equal indices, empty slices, the largest length, public fields set to anything.
Why: soundness means every safe caller, not only the ones in today's code.
-
5
Add a test for each edge and run the tests with
cargo +nightly miri test; for code calling C, use a sanitizer.Why: tools catch the cases where the argument was wrong, but only on paths a test reaches.
Common mistakes
- Accepting "the tests pass" as the argument.
- Reviewing only the lines inside
unsafe { }, not the checks and fields around them. - Allowing a safe function whose soundness depends on how today's callers use it.