safe versus unsafe Rust
Almost all Rust you write is safe Rust: the borrow checker is on, ownership is enforced, and the compiler guarantees no use-after-free, no dangling references, no data races. But some things genuinely cannot be expressed within those rules — talking to hardware, calling a C library, dereferencing a raw pointer you got from the operating system, or implementing the very building blocks (like the standard Vec) that safe code stands on. For exactly these, Rust gives you an escape hatch: the unsafe block, written unsafe { ... }, where you take responsibility for a small set of operations the compiler can no longer check for you.
It is vital to understand what unsafe actually does, because its name oversells it. unsafe does not turn off the borrow checker, the type system, ownership, or lifetimes — all of that is still fully in force inside an unsafe block. What it does is unlock five specific extra abilities that are otherwise forbidden: dereferencing a raw pointer (*const T or *mut T), calling a function marked unsafe (including foreign C functions), accessing or modifying a mutable static variable, implementing an unsafe trait, and accessing fields of a union. That is the entire list. Inside the block you are promising the compiler that you have personally checked the things it cannot — for instance, that the raw pointer you dereference really is valid and aligned. If your promise is wrong, you get undefined behaviour, exactly like in C.
Why this design is honest and useful: unsafe is not a defeat of Rust's safety, it is how Rust stays practical. The idea is to confine the uncheckable parts to small, audited, clearly marked regions, and then wrap them in a safe interface so the rest of your program — and everyone who calls your code — gets the full guarantees again. The standard library does this constantly: Vec uses unsafe internally to manage raw memory, but exposes a completely safe API. The honest caveat is the flip side: any bug in an unsafe block can corrupt memory anywhere, and Rust's promises hold only as far as your unsafe code keeps its end of the bargain. Searching for the word unsafe is therefore the first thing a reviewer does — it marks exactly the lines where the compiler stopped guaranteeing things and a human must.
let mut n = 5; let p: *mut i32 = &mut n; // a raw pointer (allowed in safe code) unsafe { *p = 10; // dereferencing a raw pointer NEEDS unsafe } println!("{n}"); // prints 10
unsafe unlocks raw-pointer deref; the type and ownership rules around it still apply.
unsafe does not disable the borrow checker or type system; it only permits five extra operations the compiler cannot verify. A bug inside unsafe can cause C-style undefined behaviour anywhere in the program, so unsafe code must be kept small and audited.