principlerustCritical
Unsafe code: when and how to use it correctly
Viewed 0 times
unsaferaw pointerundefined behaviorSAFETY commentinvariantsget_uncheckedstatic mut
Problem
Developers either avoid unsafe entirely (missing valid use cases) or use it as an escape hatch without understanding the invariants they must uphold.
Solution
Use unsafe only for the five specific operations it enables, and document safety invariants clearly:
// The 5 things only `unsafe` can do:
// 1. Dereference raw pointers
// 2. Call unsafe functions or methods
// 3. Access/modify mutable static variables
// 4. Implement unsafe traits
// 5. Access fields of unions
/// # Safety
/// `ptr` must be non-null, properly aligned, and point to
/// an initialized `i32` that outlives this call.
unsafe fn read_i32(ptr: *const i32) -> i32 {
*ptr // dereferencing raw pointer — unsafe
}
// Minimize unsafe scope
fn safe_wrapper(slice: &[i32], index: usize) -> Option<i32> {
if index < slice.len() {
// SAFETY: bounds checked above
Some(unsafe { *slice.get_unchecked(index) })
} else {
None
}
}
// Static mutable requires unsafe
static mut COUNTER: u32 = 0;
unsafe { COUNTER += 1; }Why
unsafe does not disable the borrow checker or type system — it only unlocks the five listed capabilities. The developer takes responsibility for upholding safety invariants that the compiler cannot verify. Encapsulating unsafe in safe abstractions is the goal.
Gotchas
- unsafe blocks are as small as possible — surround only the specific unsafe operation, not surrounding logic
- Document every unsafe block with a SAFETY comment explaining why the invariants hold
- Undefined behavior in unsafe code can corrupt safe code anywhere in the program — the impact is not contained to the unsafe block
Revisions (0)
No revisions yet.