48. #[expect] — Lint Suppression That Cleans Up After Itself
Silencing a lint with #[allow] is easy — but forgetting to remove it when the code changes is even easier. #[expect] suppresses a lint and warns you when it’s no longer needed.
The problem with #[allow]
You add #[allow(unused_variables)] during a refactor, the code ships, months pass, the variable gets used — and the stale #[allow] stays forever:
| |
Over time, your codebase collects these like dust. They suppress diagnostics for problems that no longer exist.
Enter #[expect]
Stabilized in Rust 1.81, #[expect] works exactly like #[allow] — but fires a warning when the lint it suppresses is never triggered:
| |
The first #[expect] is satisfied — the variable is unused, and the lint stays quiet. The second one triggers unfulfilled_lint_expectations because used isn’t actually unused. The compiler tells you: this suppression has no reason to exist.
Add a reason for future you
#[expect] supports an optional reason parameter that shows up in the warning message:
| |
When prepare_response gets called and the expectation becomes unfulfilled, the warning includes your reason — so future-you (or a teammate) knows exactly why it was there and that it’s safe to remove.
Works with Clippy too
#[expect] isn’t limited to compiler lints — it works with Clippy:
| |
This is perfect for migrating a codebase to stricter Clippy rules incrementally. Suppress violations with #[expect], fix them over time, and the compiler will tell you when each suppression can go.
#[allow] vs #[expect] — when to use which
Use #[allow] when the suppression is permanent and intentional — you never want the lint to fire here. Use #[expect] when the suppression is temporary or when you want a reminder to revisit it. Think of #[expect] as a // TODO that the compiler actually enforces.