C.W.K.
Stream
Lesson 01 of 05 · published

Unsafe Rust

~11 min · epilogue, unsafe, raw-pointers

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

Everything in this quest has been safe Rust — the borrow checker proving your code sound. unsafe is the escape hatch: a small, explicit doorway where you take responsibility for guarantees the compiler can't verify.

What unsafe actually unlocks

A common myth: unsafe turns off the borrow checker. It doesn't. It enables exactly five extra abilities — dereference a raw pointer, call an unsafe function, access or modify a mutable static, implement an unsafe trait, and access union fields. Everything else — ownership, borrowing, lifetimes — still applies inside an unsafe block. It's a narrow exception, not a free-for-all.

You take on the proof

Inside unsafe, you guarantee what the compiler normally proves: that a raw pointer is valid, that no data race occurs, that invariants hold. The compiler trusts you. That's why unsafe blocks are kept small and scrutinized — every one is a place where a bug becomes possible again, so you keep them tiny and obvious.

Wrap unsafe in a safe abstraction. The idiom is a tiny unsafe core behind a safe public API, so callers never touch unsafe themselves. Vec, Rc, and much of std are exactly this: carefully-audited unsafe internals exposing a 100% safe interface. You localize the risk, prove it once, and everyone above is safe.

When you actually need it

Most Rust programmers write little or no unsafe. You reach for it to call C libraries (FFI, next lesson), to build a data structure the borrow checker can't express, or for a specific hardware or performance need. The rule: stay in safe Rust until you have a concrete reason not to, and when you must go unsafe, wrap it so the rest of your program stays safe.

Code

unsafe unlocks raw-pointer deref, nothing more here·rust
fn main() {
    let mut num = 5;

    // Creating raw pointers is safe; DEREFERENCING them needs unsafe
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    unsafe {
        // Inside unsafe, YOU guarantee these pointers are valid
        println!("r1 points at {}", *r1);
        *r2 = 10;
    }
    println!("num is now {num}"); // 10

    // Ownership and borrowing still apply — unsafe only unlocks
    // the five specific abilities, not the whole checker.
}

External links

Exercise

Create a mutable variable, take a *const and a *mut raw pointer to it, and inside an unsafe block read through one and write through the other. Then articulate, in one sentence, exactly which guarantee you took responsibility for that the compiler would normally have proven.
Hint
You guaranteed the raw pointers point at valid, live memory and that the read/write doesn't violate aliasing. The compiler can't prove that for raw pointers, so unsafe makes you assert it — keep such blocks tiny so the assertion is easy to verify by eye.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.