Rust's signature feature is what it makes impossible: data races, use-after-free, double-free, null-pointer dereference. The borrow checker is a reviewer that doesn't get tired. What Rust doesn't catch is logic — the wrong index, the wrong predicate, the panic on a path you didn't expect.

The recurring bug shapes in Rust: panics from `unwrap()`/`expect()` on a value that turns out to be `None`, integer overflow that wraps in release builds (silent) and panics in debug builds (loud — but only if your tests cover the path), and `unsafe` blocks where the safety invariants documented in the comment don't actually hold.

Rust's tooling — `clippy`, `miri` for unsafe, `cargo audit` for dependency CVEs — does a lot. Code review focuses on what's left: the design choices, the invariants the type system can't express, and the `unsafe` boundaries.

Common pitfalls

Casual unwrap

`opt.unwrap()` on a path that can hit `None` is a panic in production. `expect("reason")` is barely better. Prefer `?`, `match`, or `unwrap_or_default()`.

Integer overflow in release

Release builds wrap silently. Use `checked_add`, `wrapping_add`, or `saturating_add` to be explicit.

Unsafe with the wrong invariants

`unsafe` is a contract you wrote with the compiler. If the invariants aren't documented and double-checked in review, undefined behavior follows.