---
title: "25.1 What unsafe actually means"
description: "The five powers unsafe unlocks, what stays checked even inside unsafe, and the safety-comment culture that treats unsafe as a contract rather than an off-switch."
url: "https://learnrust.net/chapter-25/what-unsafe-means/"
last_updated: "2026-06-13"
---

# 25.1 What unsafe actually means

For twenty-four chapters the compiler has guaranteed your programs are memory-safe: no use-after-free, no data races, no dangling pointers, all caught before the code runs. That guarantee is Rust's whole reason for being. This chapter is about the small, deliberate hole in it: the **`unsafe`** keyword, which lets you do a handful of things the compiler can't verify. The single most important idea, the one to hold onto through the whole chapter, is that `unsafe` does *not* turn off safety. It's not an off-switch. It's you telling the compiler "I'm taking responsibility for this part, because I can prove it's correct and you can't." Unsafe is a *contract*, not a license to be reckless.

## Why unsafe has to exist

It can feel like a betrayal that safe Rust has an escape hatch at all. But there are two unavoidable reasons it must.

First, the borrow checker is **conservative**: it rejects anything it can't *prove* safe, which means it also rejects some things that *are* safe but that it can't see the proof for. A handful of legitimate patterns, certain data structures, certain optimizations, are correct but beyond what static analysis can confirm. Without an escape hatch, those would be impossible to write in Rust at all.

Second, the computer underneath is fundamentally unsafe. The operating system, the hardware, and every library written in C expose raw, unchecked interfaces. To talk to any of them, your file system, the network stack, a graphics card, a C library, *something* must step outside Rust's guarantees and interface with that unchecked world. `unsafe` is the doorway. Rust's standard library itself is built on `unsafe` internally: `Vec`, `String`, every collection, manage raw memory through `unsafe` code, wrapped in safe APIs you've used all along without a thought.

## The five superpowers

`unsafe` doesn't unlock arbitrary chaos. It enables exactly **five** additional operations, and nothing else. Everything else stays exactly as checked as always. The five:

1. **Dereference a raw pointer.** Raw pointers (next lesson) can be created in safe code but only *followed* inside `unsafe`.
2. **Call an `unsafe` function.** Functions marked `unsafe` carry preconditions the caller must uphold; calling one requires `unsafe`.
3. **Access or modify a mutable `static`.** A mutable global is a data race waiting to happen, so touching one needs `unsafe`. (This is the `static` address loose end from lesson [5.1](https://learnrust.net/chapter-5/constants-and-statics/), now fully explained: a *mutable* static is exactly the case that needs unsafe.)
4. **Implement an `unsafe` trait.** Some traits (like `Send`/`Sync`, chapter 22) carry guarantees the compiler can't check, so implementing them by hand is `unsafe`.
5. **Access fields of a `union`.** A rarely-used C-interop construct where the compiler can't track which field is valid.

That's the entire list. The most important thing to notice is what's *missing* from it.

## What stays checked inside unsafe

Here is the misconception to kill right now: people imagine `unsafe { ... }` switches off the borrow checker. It does not. Inside an `unsafe` block, the borrow checker is **still running**, ownership is still enforced, types are still checked, lifetimes still apply. All your normal safety is exactly as present as outside. `unsafe` adds those five abilities on top; it subtracts *nothing*.

```rust
fn main() {
    unsafe {
        let s = String::from("hello");
        let r = &s;
        // The borrow checker is fully active here:
        // drop(s);            // would still be an error: r still borrows s
        println!("{r}");
    }
}
```

```
hello
```

So an `unsafe` block isn't a lawless zone. It's a small region where you've gained five specific powers and kept every other guarantee. This is why the right mental model is "five extra tools," not "safety off." The vast majority of your code inside an `unsafe` block is still checked exactly as rigorously as the rest of your program.

> **Key insight**
>
> `unsafe` doesn't disable Rust's safety checks; it grants five specific abilities the checker can't verify, while leaving everything else fully checked. The borrow checker, ownership, types, and lifetimes are all still active inside an `unsafe` block. What `unsafe` really means is "the compiler can't prove this part is sound, so I'm vouching for it instead." The responsibility moves from the compiler to you, for those five operations only, and that's the entire transaction.

## The safety-comment culture

Because `unsafe` shifts the burden of proof onto you, the community has a strong, near-universal convention: every `unsafe` block gets a comment explaining *why it's actually safe*, what invariant the author is relying on that the compiler couldn't see. These are called **safety comments**, conventionally written as `// SAFETY: ...`:

```rust
let numbers = vec![1, 2, 3, 4];
let ptr = numbers.as_ptr();

// SAFETY: index 2 is within bounds (the vec has 4 elements),
// and `numbers` is alive for the whole unsafe block, so the
// pointer is valid to dereference here.
let third = unsafe { *ptr.add(2) };

println!("{third}");
```

```
3
```

The `// SAFETY:` comment is the contract written down: it states the conditions under which this `unsafe` is correct, so a reviewer (or future you) can check that those conditions actually hold. This convention is load-bearing. Unsafe code that's *correct* but undocumented is a landmine, because the next person can't tell what they must preserve. The safety comment turns "trust me" into "here's exactly why, verify it." Treating `unsafe` as a contract that must be justified in writing is the difference between disciplined systems programming and the reckless pointer-juggling `unsafe` is often imagined to be.

> **Best practice**
>
> Use `unsafe` as rarely as possible, keep each `unsafe` block as small as possible (just the operations that truly need it, nothing more), and write a `// SAFETY:` comment on every one explaining why the code upholds the invariants the compiler couldn't check. Then wrap the `unsafe` in a safe API so callers never see it (the subject of lesson [25.3](https://learnrust.net/chapter-25/unsafe-functions-and-safe-abstractions/)). The goal is always to confine unsafety to a tiny, audited, well-justified core, not to spread it around.

## Quiz time

**Question #1**

Does `unsafe` turn off the borrow checker and Rust's safety guarantees? Explain.

<details class="solution">
<summary>Show solution</summary>

No. `unsafe` grants five specific extra abilities (dereferencing raw pointers, calling unsafe functions, accessing mutable statics, implementing unsafe traits, accessing union fields) but disables *nothing*. Inside an `unsafe` block the borrow checker, ownership, type checking, and lifetimes are all still fully active. `unsafe` means "I take responsibility for these five operations the compiler can't verify," not "safety off."

</details>

**Question #2**

Why does Rust need an `unsafe` escape hatch at all?

<details class="solution">
<summary>Show solution</summary>

Two reasons. (1) The borrow checker is conservative, it rejects anything it can't *prove* safe, so some genuinely-safe patterns (certain data structures and optimizations) can't be expressed in safe Rust and need an escape hatch. (2) The underlying system (OS, hardware, C libraries) exposes raw, unchecked interfaces, so interfacing with them requires stepping outside Rust's guarantees. Even the standard library (`Vec`, `String`) is built on `unsafe` internally, wrapped in safe APIs.

</details>

**Question #3**

What is a "safety comment," and why is the convention important?

<details class="solution">
<summary>Show solution</summary>

A safety comment (conventionally `// SAFETY: ...`) is a written explanation, on every `unsafe` block, of *why the code is actually sound*, the invariants the author relies on that the compiler couldn't verify. It's important because `unsafe` shifts the burden of proof from compiler to programmer, so the reasoning must be recorded for reviewers and future maintainers to check (and preserve). Correct-but-undocumented `unsafe` is a landmine; the safety comment turns "trust me" into a verifiable contract.

</details>

The first of the five superpowers is the one the others build on: raw pointers. The next lesson ([25.2](https://learnrust.net/chapter-25/raw-pointers/)) introduces them, the `*const T` and `*mut T` you can create safely but only dereference inside `unsafe`.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
