OSORIO.SYS

Understanding `unsafe` and raw pointers

unsafe is not a cheat code. It is a contract: you take responsibility for invariants the compiler can no longer prove.

This note walks through when a raw pointer is the right tool, and when it is just a way to smuggle undefined behavior into an otherwise safe crate.

What unsafe actually means

Rust still type-checks everything inside an unsafe block. What it stops doing is proving the following:

  • The pointer is aligned and non-null when you dereference it.
  • The referent is initialized for the type you claim.
  • You do not create aliasing &mut references.
  • You do not free memory that is still borrowed.

If those hold, the rest of the program can treat your API as safe.

A minimal raw-pointer example

rust
pub fn write_u32_le(dst: &mut [u8], value: u32) {
    assert!(dst.len() >= 4);
    unsafe {
        dst.as_mut_ptr().cast::<u32>().write_unaligned(value.to_le());
    }
}

The unsafe block is tiny. The assertion is the proof. That is the shape you want: a small escape hatch wrapped by a safe API.

When you actually need it

Reach for unsafe when the hardware or an FFI boundary cannot be expressed in safe Rust:

  • SIMD loads and stores through std::arch
  • Custom allocators and intrusive data structures
  • C ABI callbacks that hand you a *mut c_void

Do not use it to “make the borrow checker shut up”. That almost always means the lifetime model is wrong, not that you need raw pointers.

A practical rule

If you cannot write the safety comment in one sentence, the abstraction is too big. Split it until the invariant is local, then keep the unsafe block smaller than the comment that justifies it.