Every unsafe Block Needs a Safety Comment
unsafe without a comment is just "trust me bro" in Rust. The block compiles, the tests pass, and the one thing that keeps it sound — the invariant you checked in your head — is written down nowhere. A reviewer can't confirm it. An AI agent editing the file six months later can't confirm it. The next refactor quietly breaks the assumption and you get undefined behavior with no warning.
Why the comment carries the weight
An unsafe block doesn't disable safety. It moves the burden of proof from the compiler to you. Inside it you can dereference raw pointers, call other unsafe functions, or access a union field — operations where the compiler stops verifying the contract and trusts that you already did. The rule that makes the block sound (this index is in bounds, this pointer is non-null and aligned, this lifetime outlives the borrow) exists only in your head until you write it down.
A // SAFETY: comment writes it down. It turns an unverifiable "trust me" into a stated claim that a human or a tool can check line by line.
The rule: no comment, no merge
Put a // SAFETY: comment immediately above every unsafe block, naming the invariant the block relies on and why it holds here.
// SAFETY: `idx` is checked against `self.len` on the line above,
// so it is always in bounds for `get_unchecked`.
let elem = unsafe { self.data.get_unchecked(idx) };
| Situation | What the comment must state |
|---|---|
| Raw pointer deref | Pointer is non-null, aligned, and points to a live, initialized value |
get_unchecked / indexing | The index is provably in bounds at this point |
from_utf8_unchecked | The bytes were already validated as UTF-8 |
| FFI call | The foreign contract (ownership, nullability, thread rules) is met |
unsafe impl Send/Sync | Why concurrent access is actually sound for this type |
The mirror image also matters: a # Safety section in the doc comment of every public unsafe fn tells callers what they must guarantee before calling it. The SAFETY: comment discharges that obligation at the call site.
Enforce it in CI, not in code review
You don't have to police this by hand. Clippy ships two lints for it:
undocumented_unsafe_blocks— fires on anyunsafeblock (orunsafe impl) without a// SAFETY:comment directly above it.unnecessary_safety_comment(added in Rust 1.67.0) — the inverse: flags a// SAFETY:comment attached to code that isn't actually unsafe, so stale comments don't rot.
Turn the first into a hard gate:
#![warn(clippy::undocumented_unsafe_blocks)]
// or, to block the merge outright:
#![deny(clippy::undocumented_unsafe_blocks)]
Pair it with #![deny(unsafe_op_in_unsafe_fn)] — the standard-library policy — so that an unsafe fn body doesn't get a free pass: every unsafe operation still needs its own unsafe block, and therefore its own comment. This is exactly how the Rust standard library enforces its safety-comment policy across tens of thousands of unsafe operations.
Why this pays off with AI in the loop
Reviewers stop guessing whether a block is sound and start checking it against a written rule. So do AI agents: given the stated invariant, an agent can verify a change preserves it, or refuse the edit and flag the risk — instead of silently deleting the bounds check that the comment was quietly protecting. The comment is the machine-readable contract that keeps both humans and models honest.
Resources
- SAFETY comments — Rust Standard Library Developer Guide
undocumented_unsafe_blocks— Clippy lint referenceunnecessary_safety_comment— Clippy lint reference- Meaning of unsafe — The Rustonomicon
unsafe_op_in_unsafe_fn— The Rust Reference
Shipping Rust that AI agents touch? Yeda AI audits and hardens codebases so both people and models can reason about them.