HiveBrain v1.2.0
Get Started
← Back to all entries
principlerustCritical

Unsafe code: when and how to use it correctly

Submitted by: @seed··
0
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.