Unsafe Rust

There is a keyword in Rust that feels almost heretical given everything the language stands for: `unsafe`. It exists because Rust's safety guarantees, powerful as they are, are necessarily conservative — the compiler rejects some things that are actually fine, and some low-level operations (talking to hardware, calling C, building certain data structures) genuinely require capabilities the safe subset forbids. `unsafe` is the escape hatch, and understanding it — what it does, what it doesn't, and how to use it responsibly — completes the picture of how Rust achieves safety.

This post covers unsafe Rust — the unsafe keyword and the capabilities it unlocks. It explains why unsafe exists, what its “superpowers” are (especially raw pointers), what unsafe does not turn off, and the crucial practice of building safe abstractions over unsafe code. Understanding unsafe completes the story of Rust’s safety: safe Rust is built on a small amount of carefully-managed unsafe. Used well, it’s a controlled tool, not a hole in the guarantees.

Why unsafe exists

Unsafe Rust exists because the compiler’s safety checks are necessarily conservative, and some operations genuinely need capabilities safe Rust forbids:

Unsafe Rust exists because the safety checks are conservative (rejecting some valid programs) and some low-level operations (hardware, FFI, certain data structures) genuinely need capabilities safe Rust forbids — so unsafe is a carefully-marked escape hatch where you take responsibility for safety. It should be rare and contained. What it unlocks is a specific, limited set of powers.

What unsafe unlocks (and what it doesn’t)

The unsafe keyword unlocks five specific capabilities (“superpowers”) — and, crucially, does not turn off Rust’s other safety checks:

unsafe unlocks exactly five capabilities (dereference raw pointers, call unsafe functions, access mutable statics, implement unsafe traits, access union fields) and does not turn off the borrow checker or other safety — it’s a small, marked, contained scope, and the marking makes unsafe auditable (concentrating scrutiny). The most common superpower is raw pointers.

Raw pointers

Raw pointers (*const T and *mut T) are the most common unsafe capability — pointers without Rust’s safety guarantees, needed for low-level work:

fn main() {
    let mut num = 5;

    // Creating raw pointers is safe; dereferencing them requires `unsafe`.
    let r1 = &num as *const i32; // immutable raw pointer
    let r2 = &mut num as *mut i32; // mutable raw pointer

    unsafe {
        println!("r1 is: {}", *r1); // dereferencing requires unsafe
        *r2 = 10;
        println!("r2 is: {}", *r2);
    }
}

Raw pointers (*const T, *mut T) are the most common unsafe capability — pointers without Rust’s guarantees (can be null/dangling, ignore borrowing rules) — safe to create but requiring unsafe to dereference (where the danger is), used for FFI, low-level data structures, and performance, with safety being your responsibility. The key discipline is wrapping unsafe in safe abstractions.

Safe abstractions over unsafe

The crucial practice: wrap unsafe code in a safe abstraction — a safe API whose implementation uses unsafe but whose interface is safe and correct:

Unsafe Rust — the escape hatch unlocking five specific capabilities (chiefly raw pointers) where safe Rust’s conservative checks or low-level needs require it — does not turn off the borrow checker, is marked and auditable, and is used responsibly by wrapping it in safe abstractions (a small verified unsafe core under a large safe surface, as the standard library does). This completes how Rust achieves safety: safe Rust built on minimal, contained unsafe. Next: FFI, a major use of unsafe.

Key takeaways

Further reading

Sources & References

The unsafe keyword and raw pointers