How to Clean and Rebuild a Rust Project (cargo clean)

Run `cargo clean` to delete the `target` directory and force a complete rebuild of your project, ensuring no stale artifacts or cached metadata interfere with your build.

The binary is lying to you

You just updated a dependency in Cargo.toml. You run cargo run. The program crashes with a panic that existed in the previous version. You check the source code of the dependency. The panic is gone. You stare at the terminal. The binary is running old code.

Or maybe you switched feature flags. You added --features "json", built, and it worked. You removed the feature, built again, and the binary still has JSON support. Or you get a cryptic linker error that mentions a symbol from a crate you deleted three days ago.

The incremental compiler got confused. The cache is stale. The fix is cargo clean.

Incremental compilation and the cache

Rust compiles fast because it remembers. This is incremental compilation. Cargo tracks every source file, every dependency version, and every compiler flag. It stores this tracking data in the target directory.

When you change a file, Cargo checks the tracking data. If only one file changed, it recompiles just that file and the crates that depend on it. The rest of the project loads from the cache. This makes cargo build nearly instant for small changes.

The trade-off is state. The cache can get corrupted. Fingerprints can drift. Build scripts can produce artifacts that Cargo doesn't track. cargo clean wipes the cache. It tells Cargo to forget everything and start over.

The target directory is a cache, not a backup. Deleting it is safe.

What lives in target

The target directory holds the compiled output and the metadata that powers incremental compilation. Understanding the structure helps you know what you're deleting.

target/
├── debug/
│   ├── deps/          # Object files and intermediate artifacts
│   ├── .fingerprint/  # Hashes used to detect changes
│   └── my-binary      # The final executable
├── release/           # Same structure for release builds
└── .cargo/            # Internal Cargo state

The .fingerprint directory is the brain of incremental compilation. It contains hashes of your source files, dependency versions, and environment variables. When you run cargo build, Cargo compares current hashes against stored fingerprints. If a hash mismatches, Cargo marks that crate for recompilation.

cargo clean deletes the entire target directory. This removes the fingerprints, the object files, and the binaries. The next build has no memory of the past. It must recompile every crate in the dependency tree.

Convention aside: Developers sometimes run rm -rf target instead of cargo clean. Stick to cargo clean. It handles platform-specific paths and internal Cargo state correctly. It's the standard tool for the job.

Minimal example

The basic workflow is two commands. Clean the cache, then rebuild.

# Delete the target directory to remove all cached build artifacts.
cargo clean

# Rebuild everything from scratch.
# This will take longer than an incremental build.
cargo build

The cargo clean command removes the target directory. cargo build recreates it and compiles the project. This ensures the binary matches your current source code and dependencies.

Don't run cargo clean before every build. Incremental compilation exists for a reason. Use cargo clean only when the cache is causing problems.

Realistic scenarios

cargo clean solves specific classes of build issues. Here are the common patterns.

Stale dependency artifacts

You change a dependency version in Cargo.toml. Cargo updates Cargo.lock and downloads the new version. Sometimes the incremental compiler fails to detect that a downstream crate needs recompilation. The binary links against old object files.

# Cargo.toml
[dependencies]
serde = "1.0.193"
# After changing serde version, clean ensures the new version is fetched and compiled.
cargo clean
cargo build

This forces Cargo to recompile serde and every crate that depends on it. The binary now uses the correct version.

Build script outputs

Build scripts (build.rs) can generate code or configure the build environment. Cargo tracks build.rs changes, but it doesn't always track external files the script reads. If your build script reads a configuration file or generates code based on environment variables, and you change those inputs, Cargo might not rebuild.

/// build.rs
fn main() {
    // Generate code based on a config file.
    // If the config file changes, Cargo might not detect it.
    println!("cargo:rerun-if-changed=config.json");
}

If you forget println!("cargo:rerun-if-changed=..."), Cargo assumes the build script output is valid. cargo clean deletes the build script output. The next build runs build.rs again and picks up the changes.

Cross-compilation targets

When cross-compiling, Cargo maintains separate caches for each target triple. You might have a working x86_64-unknown-linux-gnu build but a broken aarch64-unknown-linux-gnu build. Cleaning the entire cache wastes time rebuilding the working target.

# Clean only the aarch64 target artifacts.
# The x86_64 cache remains intact.
cargo clean --target aarch64-unknown-linux-gnu

# Rebuild for the specific target.
cargo build --target aarch64-unknown-linux-gnu

This isolates the clean to the problematic target. The working target rebuilds instantly from cache.

Pitfalls and conventions

cargo clean is powerful, but it has costs. The main pitfall is time. Large projects with many dependencies can take minutes to rebuild from scratch. Running cargo clean unnecessarily slows down your development loop.

Another pitfall is misunderstanding what gets deleted. cargo clean removes the target directory. It does not remove Cargo.lock. It does not remove your source code. It does not remove the global registry of downloaded crates.

The registry lives in ~/.cargo/registry/ on Unix or %USERPROFILE%\.cargo\registry\ on Windows. cargo clean leaves the registry alone. This means downloaded crates are reused even after a clean. The rebuild is slower than an incremental build, but faster than downloading everything again.

If you need to reset dependency versions, delete Cargo.lock manually or run cargo update. cargo clean only affects build artifacts.

Convention aside: In CI/CD pipelines, it's common to run cargo clean at the start of the build process. This ensures a reproducible build state with no leftover artifacts from previous jobs. For local development, rely on incremental compilation. Clean only when stuck.

Workspaces add another layer. A workspace contains multiple crates. Running cargo clean in the workspace root cleans all member crates. Running it inside a member crate cleans only that crate.

# Clean a specific crate in a workspace.
# Other crates retain their incremental state.
cargo clean -p my-crate

This saves time in large workspaces. If only one crate is broken, clean that crate. Leave the healthy crates cached.

Run cargo clean at the root. Don't hunt for the right subdirectory.

Decision matrix

Choose the right tool based on your situation.

Use cargo clean when you suspect stale artifacts are causing incorrect behavior, such as a binary running old code or a linker error referencing a deleted crate. Use cargo clean --profile release when you only need to reset the release build and want to preserve the debug cache for faster iteration. Use cargo clean --target <triple> when cross-compiling and only one target is broken. Use cargo clean -p crate-name when a specific crate in a workspace is broken and you want to save time by preserving other crates. Reach for cargo build when you just changed code; incremental compilation handles it. Reach for cargo update when you need to change dependency versions in Cargo.lock.

Trust incremental compilation until it bites you.

Where to go next