Zero Warnings: A Practical Guide to Cleaning Up a 10,000-Line Rust Codebase

Why Zero Warnings Matters

Compiler warnings are not suggestions. They are the compiler telling you, with high confidence, that something in your code is wrong, wasteful, or inconsistent with what you likely intended. In a production Rust codebase, every ignored warning is a place where you have decided to stop listening.

In security-critical code the stakes are higher. A dead code path flagged by the compiler might be a logic branch that was supposed to execute. A redundant clone on sensitive data is the compiler noticing the lifetime model is more complicated than you think.

The Starting Point

Nine crates, roughly ten thousand lines of Rust. Running cargo build produced eight compiler warnings. Running cargo clippy produced 790 findings. The key insight: they are not the same kind of work.

Phase 1: Cargo Clippy Auto-Fix

cargo clippy --fix applies lint suggestions automatically where the transformation is unambiguous. In a 790-finding codebase, this handled roughly 340 findings in one pass: collapsible if statements, redundant clones, map_or idioms, unused imports.

cargo clippy --fix --allow-dirty --allow-staged -- -W clippy::all

Phase 2: Dead Code Audit

The compiler’s dead_code warning tells you a function or variant is defined but never used. Options: delete it, annotate with #[allow(dead_code)] with a comment, or gate with a feature flag. The judgment calls are the slow part.

Phase 3: const fn Sweep

Adding const to qualifying functions enables compile-time evaluation. The clippy::missing_const_for_fn lint identified 110 functions. Constraints: no heap allocation, no format!, no trait method calls in const context. Batch-and-check workflow: add const, build, let the compiler reject invalid ones, repeat.

Phase 4: Pedantic Clippy

After default lints are clean, clippy::pedantic and clippy::nursery surface genuine quality improvements: missing #[must_use] attributes, derive(PartialEq) without Eq, naming convention violations.

Phase 5: Flaky Test Elimination

Three tests failed intermittently due to nanosecond timestamp collision. Two tests running in parallel could generate identical temp directory names. The fix: an atomic counter appended to the timestamp.

static COUNTER: AtomicU64 = AtomicU64::new(0);
fn unique_name() -> String {
    let n = COUNTER.fetch_add(1, Ordering::Relaxed);
    format!("test-{}-{}", nanos(), n)
}

The Result

Zero compiler warnings across nine crates. 428 passing tests with zero flakes. The ongoing cost is near zero — with -D warnings in CI, any new warning fails the build immediately.

Scroll to Top