Skip to content

Rust Unsafe

Every guarantee this course has talked about — no dangling references, no data races, no out-of-bounds access — is enforced by the compiler. unsafe is how you tell the compiler “let me do something you can’t verify is safe; I’m taking responsibility for it instead.”

A common misconception is that unsafe turns off Rust’s safety checks entirely. It doesn’t. Inside an unsafe block, you can do exactly five extra things that are otherwise forbidden:

  1. Dereference a raw pointer
  2. Call an unsafe function
  3. Access or modify a mutable static variable
  4. Implement an unsafe trait
  5. Access fields of a union

Everything else — the borrowing rules, move semantics, type checking — still fully applies inside unsafe code. It’s a scalpel for five specific operations, not an off switch.

Rust’s normal references (& and &mut) always point to valid, properly aligned data — that’s part of what the compiler guarantees. A raw pointer (*const T or *mut T) drops that guarantee, which is why reading one requires unsafe:

fn main() {
let value = 5;
let pointer = &value as *const i32;
unsafe {
println!("{}", *pointer);
}
}

Output:

5
▶ Try it Yourself

You can create a raw pointer in safe code — it’s only dereferencing it (reading the value it points to) that needs unsafe, since the compiler can no longer verify the pointer is valid.

Some functions are themselves marked unsafe because they have requirements the compiler can’t check for you — calling them is your promise that you’ve verified those requirements yourself:

unsafe fn dangerous() {
println!("this function trusts you to have checked its requirements");
}
fn main() {
unsafe {
dangerous();
}
}

Output:

this function trusts you to have checked its requirements
▶ Try it Yourself

If Rust is all about safety, why allow this at all? Because some things are genuinely impossible to prove safe at compile time, even though they’re perfectly safe in practice — talking directly to hardware, calling into a C library, or building certain high-performance data structures. unsafe lets the standard library (and, rarely, your own code) implement these low-level pieces, while everything built on top of them stays in ordinary, fully-checked safe Rust.

Writing unsafe doesn’t mean your code is now buggy — it means the compiler can no longer catch certain classes of mistakes for you in that specific block, and it’s on you to uphold the same guarantees by hand. Most experienced Rust developers write very little unsafe code directly; it tends to live in small, carefully-reviewed pockets at the bottom of a library, wrapped in a safe API for everyone else to use.

  • unsafe unlocks exactly five operations (mainly raw pointers and calling unsafe functions) — everything else is still fully checked.
  • A raw pointer (*const T / *mut T) can be created in safe code; dereferencing it requires unsafe.
  • unsafe exists so low-level code (hardware access, C interop) can be built, while code using it stays safe.
  • As a beginner, you’ll rarely need to write it yourself — reach for a safe abstraction first.

Quick check

1. What does an unsafe block actually unlock?

2. Can you create a raw pointer in safe code?

3. As a beginner, how often should you expect to write unsafe yourself?

4. Does writing unsafe code mean your program now has bugs?

Score: 0 / 4