Freeing Up Gigabytes: Reclaiming Disk Space from Rust Cargo Builds

Updated April 22, 2025 · 25 min ·  

Rust Cargo Disk Space Windows Linux MacOS

Cover Image

Summary

Rust's powerful build tool, Cargo, often silently accumulates significant disk space within its target directories, which house the compiled build artifacts of its projects. This article delves into various techniques for reclaiming this disk space. By choosing techniques that align with priorities, Rust developers can effectively balance disk usage and efficient build processes.

The other day, while running incremental backups on my laptop, I was struck by a familiar yet unwelcome sight: a steady stream of changed files. Despite primarily working on just a few personal Rust projects in my free time, the sheer volume of modifications was significant. It brought back vivid memories of my past Node.js projects and the seemingly ever-expanding universe of node_modules folders.

Digging deeper, the culprit quickly revealed itself: the seemingly innocuous target directory. As the central hub for all build artifacts (compiled binaries, libraries, and intermediate build files) generated by Cargo, Rust’s powerful build system, this folder had quietly ballooned in size. My inner cheapskate cried out! Faster backups, more efficient disk usage – surely Rust, with its reputation for efficiency, wasn’t inherently destined to sprawl across my precious storage?

If you’re a fellow Rust developer or a Linux enthusiast running cutting-edge builds on various Rust tools (think gems like bat, fd, ripgrep, and starship), you might also be grappling with this silent disk space hog. After all, the contents of the target folder are ephemeral, easily regenerated by Cargo when needed. So, how can we reclaim valuable disk space in our Rust projects, speed up our backups, and encourage a more parsimonious Rust development footprint?

In this article, I’ll share practical steps you can take to reclaim disk space consumed by Rust, specifically focusing on managing the often-overlooked target directory and its impact on our systems. Let’s make Rust development a little lighter on our storage devices!

Building a testbed to observe Rust build artifacts and disk usage

To truly understand the disk space implications of Cargo builds and to effectively test our strategies for reclaiming that space, we need to create an area where we can conduct tests. In this section, we’ll set up a testing ground where we can examine the artifacts generated during Rust compilation. We’ll begin by creating a temporary directory and populating it with a few Rust projects, ensuring we have build outputs to work with.

Create project directories

First, we’ll create a directory:

mkdir tmp
cd tmp

To populate our testing environment with build outputs, we’ll clone a couple of my Rust projects. I chose these since they build relatively quickly, but will enable us to observe and ultimately clean the build artifacts.

git clone https://github.com/thisdavej/human-time-cli.git
git clone https://github.com/thisdavej/battinfo.git

Next, let’s add some nested directories so we can ultimately test how to recursively remove Rust build artifacts when all projects are not simply housed in one root folder.

# The `-p` creates parent directories if they don't exist, allowing us to
# create multiple directory levels in one shot.
mkdir -p level2/level3

We are now positioned to clone a couple of additional GitHub repos in these subdirectories:

cd level2
git clone https://github.com/sharkdp/bat.git

cd level3
git clone https://github.com/marshallpierce/rust-base64.git

Excellent - we have created a solid testing foundation.

Here’s a high-level view of our directory structure without the detailed folders underneath for each Rust project:

tmp/
├── battinfo
├── human-time-cli
└── level2
    ├── bat
    └── level3
        └── rust-base64

Create script to build multiple Rust projects

As a next step, let’s create bash script in the root of the tmp folder so we can build all our Rust projects in one go. We’ll use this script multiple times so we can observe the various Rust build clean up options in action. We’ll call this script cargo-build-all:

#!/usr/bin/env bash

# file: cargo-build-all

projects=(
  "battinfo"
  "human-time-cli"
  "level2/bat"
  "level2/level3/rust-base64"
)

for dir in "${projects[@]}"; do
  echo "🔨 Building $dir"
  if ! (cd "$dir" && cargo build); then
    echo "❌ Build failed in $dir"
  fi
done
echo "✅ All builds completed."

Next, we make our bash script executable:

chmod u+x cargo-build-all

Finally, we run our script and watch Cargo build projects in rapid succession. The script output will look something like this, but I have removed a lot of the intermediate output for brevity:

$ ./cargo-build-all
🔨 Building battinfo
🔨 Building human-time-cli
🔨 Building level2/bat
🔨 Building level2/level3/rust-base64
✅ All builds completed.

Create script to summarize disk space consumed by Cargo builds

Having built each Rust project using cargo build using the script we created above, let’s examine the outcome of these builds. As anticipated, each project now contains a target directory, the standard location for build artifacts. Our next step is to assess the disk space occupied by these target directories. This will provide a clear measure of the storage that could potentially be recovered.

Before creating a bash script to show the sizes of the target directories associated with each project, we can simply run du to get a high level view of the space consumed. We employ a regex with grep (grep target$) to ensure that only the disk space for the entire target directory is shown and not all of the individual files under the target directory.

du -h | grep target$
145M    ./human-time-cli/target
155M    ./battinfo/target
5.4M    ./level2/level3/rust-base64/target
770M    ./level2/bat/target

To achieve a more visually appealing and easily repeatable summary of these target directory sizes (along with a total), we create a handy script called cargo-sizes:

#!/usr/bin/env bash

# file: cargo-sizes

echo "📦 Finding and summarizing all Rust target/ directories..."
echo

find . -type d -name target -prune -print0 | while IFS= read -r -d '' dir; do
  size=$(du -s "$dir" 2>/dev/null | awk '{printf "%.1f", $1/1024}')
  echo "  ${dir#./}$size MB"
done

# Get the grand total in kilobytes
total=$(find . -type d -name target -prune -print0 | \
    xargs -0 du -s 2>/dev/null | \
    awk '{sum+=$1} END {printf "%.1f", sum/1024}')

echo
echo "🧮 Total ${total} MB"

… and, as usual, we make our script executable:

chmod u+x cargo-sizes

We can then run the script to see the sizes of each target directory along with the total space consumed by the Rust build artifacts:

$ ./cargo-sizes
📦 Finding and summarizing all Rust target/ directories...

  human-time-cli/target → 144.5 MB
  battinfo/target → 154.7 MB
  level2/level3/rust-base64/target → 5.4 MB
  level2/bat/target → 769.2 MB

🧮 Total 1073.8 MB

So, we’re looking at a total of 1072.8 MB (1.073 GB) that could be reclaimed for these four Rust projects.

Motivation to clean up (or not clean up) space

While the accumulated disk space within target directories can become substantial, especially across multiple projects, the act of cleaning them isn’t without its considerations. On one hand, freeing up this space provides more disk space and accelerates incremental backups. Conversely, removing the target directory means that the next time the project is built, it and all its dependencies will have to be compiled again, adding to the build time. Therefore, the decision to clean or not clean often boils down to individual priorities and workflow.

From my perspective, the disk space gained by deleting the target directory usually outweighs the time needed for a subsequent rebuild, as it’s often not excessively long. Let’s now investigate methods for optimizing disk space usage.

Strategies for managing disk footprint of Rust target directories

Now that we’ve seen the potential disk space accumulation within Rust project target directories, let’s explore various strategies for keeping this under control. We’ll delve into four distinct approaches, each with its own set of trade-offs between disk space savings and build performance.

Option 1: Ignore target directories when backing up files

Instead of deleting the space-consuming target directories and incurring rebuilds, this approach simply excludes them from our backups and speeds up the backup process.

For example, let’s explore how we can create a backup mirror of our Rust projects and exclude the target directories using rsync. We start by including a --dry-run option to not actually copy or delete anything but show what would happen.

rsync -av --dry-run --progress --delete --exclude='target/' $HOME/dev/rust/ /mnt/nasbox/backups/rust

Here’s a breakdown of rsync options I’m using here:

  • -a: archive mode
    • Preserves file structure, permissions, symlinks, timestamps, etc.
    • Recursive copy by default
  • -v: verbose
    • Lists what’s being copied
  • –dry-run:
    • Doesn’t actually copy or delete anything — just shows what would happen
    • Great for testing before running for real
  • –progress:
    • Shows file-by-file progress (size, %, etc.)
    • Only useful when actually copying (less useful with –dry-run, but harmless) –delete:
    • ⚠️ Deletes files in the destination ($HOME/backups/rust) that don’t exist in the source ($HOME/tmp/)
    • Makes the destination a true mirror of the source
  • –exclude=‘target/’:
    • Skips all target directories and their contents

When we’re satisfied that our backups will copy the files as expected, we can remove the --dry-run option:

rsync -av --dry-run --progress --delete --exclude='target/' $HOME/dev/rust/ /mnt/nasbox/backups/rust

Of course, the strategy of excluding the target directory isn’t limited to rsync; it’s a common feature found in various backup utilities like rclone, syncthing, and robocopy on Windows, among others.

Our strategy here of excluding all target directories makes an inductive leap: it assumes that every directory called target relates to Rust build artifacts. While this is often a safe bet, it’s important to acknowledge the possibility of other, non-Rust related target directories existing elsewhere on your system, which would then be inadvertently excluded from your backups.


Bonus info: Leveraging CACHEDIR.TAG for exclusion of the target directory in backups

Commenting on this article on Reddit, Josh Triplett shared an important detail: a common feature (or default behavior) among various backup tools is to automatically exclude any directory containing a CACHEDIR.TAG file. The presence of a CACHEDIR.TAG file signals to tools and applications that a particular directory is intended to be used as a cache directory. Cargo places a CACHEDIR.TAG file in its target directory, opening the door for the exclusion of the target directory.

Unlike rsync, which doesn’t recognize CACHEDIR.TAG files, other tools like GNU Tar offer built-in support. For instance, GNU Tar’s --exclude-caches option allows us to specifically skip directories marked with this tag, as demonstrated when creating a gzipped tarball of a Rust project and excluding its target directory:

$ tar --exclude-caches -czvf battinfo.tar.gz battinfo/
# ... (showing only some of the files for brevity) ...
battinfo/Cargo.lock
battinfo/target/
tar: battinfo/target/: contains a cache directory tag CACHEDIR.TAG; contents not dumped
battinfo/target/CACHEDIR.TAG

The preceding command’s output illustrates that while GNU tar’s --exclude-caches option skips the contents of the target directory (due to the presence of the CACHEDIR.TAG file), it still includes the CACHEDIR.TAG file itself in the backup. For a complete exclusion, GNU tar also offers --exclude-caches-all, which omits both the directory and its CACHEDIR.TAG file.

Option 2: Recursive build artifact removal with cargo clean

While excluding target directories from backups is a passive approach, Cargo itself provides a built-in mechanism for actively removing build artifacts: the cargo clean command. This option offers a more direct and Rust-aware way to reclaim disk space. Notably, cargo clean also intelligently handles workspaces, ensuring that target directories across all member crates are properly cleaned, even when they reside in different locations relative to the root Cargo.toml file for a given project. Let’s explore how we can leverage the find command (or its PowerShell equivalent) to recursively invoke cargo clean across our Rust projects.

On Linux/macOS systems, we can use the find command along with execdir to recursively remove Rust build artifacts:

find -type d -name target -execdir cargo clean \;

Explanation:

  • find: Starts searching from the current directory (.).
  • -type d: Ensures only directories are considered in the search.
  • -name target: Looks specifically for directories named target, which is where Rust’s build artifacts are stored.
  • -execdir cargo clean \;: For each matching directory, it runs the cargo clean command in the directory where the target folder is found.

To ensure we don’t have to remember this command each time, we could create a bash script in our system path (e.g. ~/bin/crate-cleaner.sh) or create an alias:

alias cargo-clean-recursive='find -type d -name target -execdir cargo clean \;'

We can also accomplish the same goal using PowerShell on Windows. For example, we could create a fle called .\clean-all.ps1:

Get-ChildItem -Recurse -Directory -Filter target |
    Select-Object -ExpandProperty Parent -Unique |
    ForEach-Object {
        Write-Host "Cleaning in $($_.FullName)"
        Push-Location $_.FullName
        cargo clean
        Pop-Location
    }

The syntax is a bit verbose, but this PowerShell script effectively gets the job done. We invoke it in the root folder containing our cargo projects like this:

.\clean-all.ps1

To make this command even more accessible, we can create an alias:

Set-Alias cargo-clean-recursive .\clean-all.ps1

Additionally, we can store this Set-Alias command in our PowerShell profile so the cargo cleaning command will be readily available.

While the method described above (Linux/macOS or PowerShell) effectively cleans all target directories, what if we desire more granular control and the ability to exclude specific projects? Although we could enhance our existing bash or PowerShell scripts, the next option offers a more straightforward and arguably more elegant solution for selective cleanup.

Option 3: The cargo-clean-all crate

There are multiple Rust crates available on crates.io for cleaning Cargo build artifacts, but the cargo-clean-all crate is my favorite. It’s fast and provides a number of useful options including the ability to salvage executable files in the target directory while wiping the remaining build artifacts.

For starters, let’s install the crate:

cargo install cargo-clean-all

This crate adds a new subcommand to our cargo ecosystem that can be invoked with cargo clean-all. We’ll use this in just a moment.

Before exploring this new option, and because we previously removed our build artifacts using the cargo clean method, we’ll first rebuild our projects using the cargo-build-all script to re-populate the target directories.

$ ./cargo-build-all
🔨 Building battinfo
🔨 Building human-time-cli
🔨 Building level2/bat
🔨 Building level2/level3/rust-base64
✅ All builds completed.

Next, we run the newly installed cargo clean-all command without arguments:

$ cargo clean-all
Computing size of target/ for project
Ignoring the following project directories:

Selected the following project directories for cleaning:
rust-base64: 6.46 MB (2025-04-14 18:12), /home/dave/tmp/level2/level3/rust-base64
human-time-cli: 185.77 MB (2025-04-14 17:06), /home/dave/tmp/human-time-cli
battinfo: 199.4 MB (2025-04-14 18:11), /home/dave/tmp/battinfo
bat: 989.03 MB (2025-04-14 18:12), /home/dave/tmp/level2/bat

Selected 4/4 projects, cleaning will free: 1.38 GB. Keeping: 0 B
Clean the project directories shown above? [y/n]

At this point, we could type y followed by the Enter key to clean the directories; however, instead type n followed by the Enter key so we can explore the interactive mode (-i). Enter the following command:

$ cargo clean-all -i
Computing size of target/ for project
Select projects to clean:
> [x] rust-base64: 6.46 MB (2025-04-14 18:12), /home/dave/tmp/level2/level3/rust-base64
  [x] human-time-cli: 185.77 MB (2025-04-14 17:06), /home/dave/tmp/human-time-cli
  [x] battinfo: 199.4 MB (2025-04-14 18:11), /home/dave/tmp/battinfo
  [x] bat: 989.03 MB (2025-04-14 18:12), /home/dave/tmp/level2/bat

As shown above, this command launches a user-friendly interactive interface, allowing us to precisely choose which Rust projects should have their build artifacts removed.

  • Use the up/down arrow keys to move through the list
  • Press the space bar to toggle selection
  • Press Enter to confirm choices.
  • Finally, type y and press Enter to execute the cleaning process for the selected projects.

Very nice!

Upon subsequent invocations of cargo clean-all or cargo clean-all -i, you will observe that cargo clean-all will intelligently identify and operate only on remaining projects with Rust build artifacts.

The cargo-clean-all crate also includes other helpful options such as cargo clean-all -y that will blaze through and clean all detected projects. I’m planning to include cargo clean-all -y at the start of my backup script. This will automatically remove the Rust build artifacts before rsync runs the backup.

Also, don’t miss the super useful -e (--keep-executable) option to retain the compiled executables in the release, debug and cross-compilation directories. When I executed cargo clean-all -e specifically for my battinfo project, it created an executables directory and relocated my target executable there before deleting the remaining build artifacts. The new structure looks like this with the added executables directory:

tree battinfo/
battinfo/
├── Cargo.lock
├── Cargo.toml
├── executables
│   └── debug
│       └── battinfo
├── LICENSE-MIT
├── README.md
├── src
│   ├── args.rs
│   ├── lib.rs
│   └── main.rs
└── tests
    └── tests.rs

5 directories, 9 files

For your reference, here are all of the cargo clean-all options currently available:

$ cargo clean-all [OPTIONS] [DIR]

Arguments:
  [DIR]  The directory in which the projects will be searched [default: .]

Options:
  -y, --yes                Don't ask for confirmation; Just clean all detected projects that are not excluded by other constraints
  -s, --keep-size <SIZE>   Ignore projects with a target dir size smaller than the specified value. The size can be specified using binary prefixes like "10MB" for 10_000_000 bytes, or "1KiB" for 1_024 bytes [default: 0]
  -d, --keep-days <DAYS>   Ignore projects that have been compiled in the last [DAYS] days. The last compilation time is inferred by the last modified time of the contents of target directory [default: 0]
      --dry-run            Just collect the cleanable projects and list the freeable space, but don't delete anything
  -t, --threads <THREADS>  The number of threads to use for directory scanning. 0 automatically selects the number of threads [default: 0]
  -v, --verbose            Show access errors that occur while scanning. By default those errors are hidden
  -i, --interactive        Use the interactive project selection. This will show a selection of all cleanable projects with the possibility to manually select or deselect
      --ignore <IGNORE>    Directories that should be ignored by default, including subdirectories. This will still detect the projects in those directories, but mark them to not be cleaned. To actually skip scanning directories, use --skip instead. The directories can be specified as absolute paths or relative to the workdir
  -e, --keep-executable    Keeping compiled executables in release, debug and cross-compilation directories. Moves the executable to a new folder outside of target
      --skip <SKIP>        Directories that should be fully skipped during scanning, including subdirectories. This will speed up the scanning time by not doing any reads for the specified directories. The directories can be specified as absolute paths or relative to the workdir
      --depth <DEPTH>      Maximum depth of subdirectories that should be scanned looking for the **`target/`**. This will speed up the scanning The option is for target/ dir, NOT for the project dir 0 means no limit [default: 0]
      --keep-empty-target  Keep the empty target dir and remove only the files and subdirectories inside instead of removing the directory itself
  -h, --help               Print help
  -V, --version            Print version

Option 4: Create global target directory with CARGO_TARGET_DIR

As described in previous sections, when we build a Rust project, Cargo faithfully creates a target directory right alongside our Cargo.toml file. This keeps the build outputs neatly tucked away within the project’s boundaries. While this keeps things nicely contained within each project, it can lead to some duplication on disk, especially if the rust projects share dependencies.

Fortunately, Rust provides a handy way to centralize these build artifacts using the CARGO_TARGET_DIR environment variable as one target to rule them all. This allows us to specify a single global target directory where Cargo will place the target directories for all of our Rust projects.

By setting this variable, we tell Cargo to create the target directory (and its internal structure) within the location we specify, regardless of where our Cargo.toml file resides for each project.

We can set this environment variable in a few ways:

  1. For the current shell session: Use the export command as shown below to set the CARGO_TARGET_DIR variable. Any subsequent Cargo commands in that terminal session will use the specified “global target” directory.

    export CARGO_TARGET_DIR="$HOME/builds/rust-target"
    cd battinfo
    cargo build
    cd ../human-time-cli
    cargo build
    
  2. Persistently (User-Level): We can configure CARGO_TARGET_DIR globally by editing the Cargo configuration file, usually located at $HOME/.cargo/config.toml (on Unix-like systems) or %USERPROFILE%\.cargo\config.toml (on Windows). Add the following section:

    [build]
    target-dir = "/home/dave/builds/rust-target"
    

    📝 Note: I found that using environment variables like $HOME or ~ within the config.toml didn’t work as expected. I had to use the full, hardcoded path to my home directory.

  3. Persistently (Project-Level): We can also set a specific CARGO_TARGET_DIR for a particular project by creating a .cargo directory in the project’s root and adding a config.toml file with the [build] section as shown above. Project-level settings will override user-level configurations.

Let’s go with the user-level configuration for the purpose of this article. By configuring CARGO_TARGET_DIR globally within $HOME/.cargo/config.toml and setting its value to /home/dave/builds/rust-target, subsequent builds of our Rust projects will automatically place their output in this central location. For example, after running this…

cd battinfo
cargo build
cd ../human-time-cli
cargo build

… the target directories for both battinfo and human-time-cli will be created inside /home/dave/builds/rust-target.

We’ll then find the following directory structure (sans files and the out subdirectories) in the specified global directory containing the executables and dependencies for both projects:

rust-target/
└── debug
    ├── build
    │   ├── battery-15fdfe8af6b1f341
    │   ├── battery-3a75f4d1c9050e40
    │   ├── libc-2908ee2b42bb665c
    │   ├── libc-f45ca5ba4730bfac
    │   ├── num-traits-0495719f19eed353
    │   ├── num-traits-6514fca58c9b8c0c
    │   ├── proc-macro2-0c3b9fcc96165103
    │   ├── proc-macro2-811184542e54f117
    │   ├── serde-3f6c1d8b2a3f5028
    │   ├── serde-6c2f97efd2fac44f
    │   ├── serde_json-046af6a1e23e734c
    │   ├── serde_json-56812e5bec70ceb9
    │   ├── syn-4ce1710243ee8455
    │   ├── syn-955c812b9c1e1757
    │   ├── typenum-818ebfe7fcf88d84
    │   └── typenum-fd16e0fdc0485eed
    ├── deps
    ├── examples
    └── incremental
        ├── battinfo-1ccjis0l7suta
        │   └── s-h6iwuckg5d-0owdx2o-8lg5o0k4ezzcn8ly6e6qfcv28
        ├── battinfo-3ukjo3p21be8e
        │   └── s-h6iwucpc4k-088n7ze-6pztx75mvrlzmuu6ebcxm5peb
        ├── human_time-1e03rk3t6fn6r
        │   └── s-h6iwuwxsc8-14g485g-7by3c24m47wx55tpzqofpjr0g
        └── human_time_cli-24p21v1fjfemf
            └── s-h6iwuwrmv5-0rt24xg-5u8cayr2smz0t8rcycw6jjgs7

As you can see, Cargo creates separate subdirectories within our centralized target directory, named after our projects (often with an appended hash to ensure uniqueness). This keeps the build artifacts for each project distinct, even though they reside in the same top-level directory.

One of the potential benefits of centralizing our target directories is the possibility of saving disk space. If multiple projects depend on the same version of the same libraries, Cargo can build and store those dependencies only once in the shared CARGO_TARGET_DIR, rather than having them duplicated in each project’s local target directory.

Before centralizing, the individual target directories of all four projects occupied around 1073.8 MB (shown earlier in this article).

Let’s ascertain the space occupied by the global target directory:

# Do with 1 decimal place to match the precision
du -s rust-target/ | awk '{printf "%.1f MB\n", $1 / 1024}'
1003.6 MB

The space required for the global target directory is definitely less with a size of 1003.6 MB.

We can then calculate the difference between target directories in each Rust project versus a global target directory:

qalc 1073.8 MB - 1003.6 MB
(1073.8 megabytes)(1003.6 megabytes) = 70.2 MB

In this example with four Rust projects, I saved around 70.2 MB. This saving can become significantly larger as you work on more projects with shared dependencies.

Another advantage of using a centralized CARGO_TARGET_DIR becomes apparent when sharing Rust code with others. Since the build artifacts are located elsewhere, we can more easily zip just the source code (and the Cargo.toml and Cargo.lock files) without specifically excluding the often large target directory.

Otherwise, we need to go to greater lengths when creating a zip file, for example, by explicitly excluding the target directory. Here’s a way to achieve this when you’re in the root directory of the project you want to compress. In this example, I instruct zip to not only exclude the target directory, but also exclude the .git directory.

$ zip -r battinfo.zip . -x "target/*" -x ".git/*"
  adding: .gitignore (deflated 2%)
  adding: Cargo.toml (deflated 34%)
  adding: LICENSE-MIT (deflated 41%)
  adding: README.md (deflated 64%)
  adding: src/ (stored 0%)
  adding: src/args.rs (deflated 72%)
  adding: src/lib.rs (deflated 78%)
  adding: src/main.rs (deflated 74%)
  adding: tests/ (stored 0%)
  adding: tests/tests.rs (deflated 75%)
  adding: Cargo.lock (deflated 72%)

Back to considering the pros and cons of using a global target directory, there is a crucial trade-off to be aware of: the behavior of cargo clean. When you run cargo clean in any of your projects, it will delete the entire directory specified by CARGO_TARGET_DIR rather than just the target directory associated with the current project.

This means that you’ll lose all the build artifacts for all your Rust projects that are sharing this central location. This includes incremental compilation data, which can slow down your next builds across all those projects until the target directory is rebuilt.

For this reason, I personally prefer a single $CARGO_TARGET_DIR and only run a cargo clean on a given project (which essentially cleans all projects) every few weeks, typically after a Rust toolchain update using rustup.

📝 Note: Speaking of rustup, it’s essential to keep your Rust toolchain updated on a regular basis to benefit from the latest features, performance improvements, and security fixes. Here’s how you can update Rust using rustup:

rustup self update
rustup update

The first command, rustup self update, updates the rustup tool itself. The second command, rustup update, updates your installed Rust toolchains (stable, beta, nightly, etc.) to the latest versions.

Wrapping up this section, CARGO_TARGET_DIR offers a powerful way to manage our Rust build artifacts in a centralized location, potentially saving disk space and simplifying code sharing. Just be mindful of the implications of cargo clean and consider if the benefits outweigh the potential inconvenience for your workflow.

Update (2025-04-22): Good news from Josh Triplett, a Rust project team developer, who commented on this article on Reddit. The Rust team is working on a better way to handle build outputs with something called “build directories.” This new system will let us put intermediate files in a separate location but keep the final executables in the target directory (so scripts don’t break). Plus, it will organize build artifacts by project, and make it possible to clean up build files for individual projects.

A second large consumer: cleaning the cargo cache

Beyond the build artifacts in the target directory, Cargo also maintains another cache for efficiency. This cache typically resides in ~/.cargo on Linux and macOS, and %USERPROFILE%\.cargo on Windows. This directory stores downloaded crate information and Git repositories of dependencies.

To inspect and manage this cache, we can use the cargo-cache utility.

Let’s first install cargo-cache:

cargo install cargo-cache

Once installed, we can use the cargo cache command to see much space the Cargo cache is currently consuming:

$ cargo cache
Cargo cache '/home/dave/.cargo':

Total:                              1.75 GB
  38 installed binaries:          407.59 MB
  Registry:                         1.34 GB
    3 registry indices:           632.28 MB
    3702 crate archives:          462.84 MB
    493 crate source checkouts:   240.34 MB
  Git db:                          10.25 MB
    6 bare git repos:               7.44 MB
    3 git repo checkouts:           2.81 MB

This output shows a breakdown of the space used by installed binaries, the registry cache (which includes downloaded crate archives, source checkouts, and index files), and the Git database for dependencies.

While cleaning the Cargo cache can free up disk space, it comes with a drawback: slower future builds.

When we clean the cache, we’re essentially removing the downloaded copies of crates from the registry (~/.cargo/registry) and the cached Git repositories (~/.cargo/git). This means that the next time we build a project that depends on these crates, Cargo will have to redownload them from crates.io or the respective Git repositories, even if we’ve used them in other projects before.

If you have a slow or metered internet connection, this re-downloading process can be time-consuming and potentially costly. Therefore, if you have ample disk space and a slow or metered internet connection, cleaning the Cargo cache might not be the most sensible option.

It’s crucial to understand that cargo cache does not remove the target/ directories. As we discussed earlier, the target/ directory is where the compiled build artifacts reside and often accounts for the bulk of disk usage related to Rust projects. If your primary goal is to free up disk space, using cargo clean will provide the biggest bang for your buck.

cargo-cache provides several options for cleaning:

  • cargo cache --autoclean: This command attempts to intelligently clean the cache by removing unused or old entries. We can use the --dry-run flag to see what would be removed without actually deleting anything:

    $ cargo cache --autoclean --dry-run
    Clearing cache...
    
    dry-run: would remove: '/home/dave/.cargo/registry/src' (241.05 MB)
    dry-run: would remove: '/home/dave/.cargo/git/checkouts' (2.81 MB)
    Cargo cache '/home/dave/.cargo':
    
    Total:                               1.75 GB
      38 installed binaries:           407.59 MB
      Registry:                          1.34 GB
        3 registry indices:            632.28 MB
        3702 crate archives:           462.84 MB
        493 crate source checkouts:    240.34 MB
      Git db:                           10.25 MB
        6 bare git repos:                7.44 MB
        3 git repo checkouts:            2.81 MB
    
  • cargo cache --remove-dir <directory>: This allows us to remove specific parts of the cache, such as registry, git, or bin.

  • cargo cache --remove-dir all: This is the nuclear option, removing the entire Cargo cache (excluding installed binaries). Again, we can use --dry-run to preview the changes:

    $ cargo cache --remove-dir all --dry-run
    
    dry-run: would remove: '/home/dave/.cargo/git/db' (7.44 MB)
    dry-run: would remove: '/home/dave/.cargo/git/checkouts' (2.81 MB)
    dry-run: would remove: '/home/dave/.cargo/registry/src' (240.34 MB)
    dry-run: would remove: '/home/dave/.cargo/registry/cache' (462.84 MB)
    dry-run: would remove: '/home/dave/.cargo/registry/index' (632.28 MB)
    dry-run: would remove in total: 1.35 GB
    Cargo cache '/home/dave/.cargo':
    
    Total:                               1.75 GB
      38 installed binaries:           407.59 MB
      Registry:                          1.34 GB
        3 registry indices:            632.28 MB
        3702 crate archives:           462.84 MB
        493 crate source checkouts:    240.34 MB
      Git db:                           10.25 MB
        6 bare git repos:                7.44 MB
        3 git repo checkouts:            2.81 MB
    

    To actually perform the removal, omit the --dry-run flag:

    cargo cache --remove-dir all
    removing: '/home/dave/.cargo/git/db'
    removing: '/home/dave/.cargo/git/checkouts'
    removing: '/home/dave/.cargo/registry/src'
    removing: '/home/dave/.cargo/registry/cache'
    removing: '/home/dave/.cargo/registry/index'
    Cargo cache '/home/dave/.cargo':
    
    Total:                                  1.75 GB => 407.59 MB
      38 installed binaries:                           407.59 MB
      Registry:                                  1.34 GB => 0  B
        3 registry indices:                    632.28 MB => 0  B
        3702 => 0 crate archives:              462.84 MB => 0  B
        493 => 0 crate source checkouts:       240.34 MB => 0  B
      Git db:                                   10.25 MB => 0  B
        6 => 0 bare git repos:                   7.44 MB => 0  B
        3 => 0 git repo checkouts:               2.81 MB => 0  B
    
    Size changed 1.75 GB => 407.59 MB (-1.35 GB, -76.75%)
    

By cleaning the Cargo cache, we reduced disk usage by 1.35 GB. In real-world scenarios, this number is likely to be significantly higher, as I had already cleared the cache a few times while preparing this article for you all.

The existence of the ~/.cargo directory and its contents brings up an interesting point for backups. If you’re seeking to back up your Rust dev environment settings, you might consider backing up the configuration files within ~/.cargo (like config.toml) while potentially excluding the larger subdirectories like registry and git to save space, especially if you have a reliable internet connection to redownload dependencies when needed.

For example, using rsync, you could back up only the top-level files and exclude all directories within ~/.cargo with the exception of ~/.cargo/bin (which contains the global binaries installed with cargo install):

rsync -av \
  --include='/bin/***' \
  --include='/*' \
  --exclude='/**' \
  ~/.cargo/ /mnt/backup/cargo_backup/

This would back up your key Cargo settings (and global binaries) without including the potentially large cache directories.

The cargo-cache crate is useful for managing the Cargo cache and reclaiming disk space; however, be mindful of the potential impact on future build times due to the need to redownload dependencies. Choose the cleaning strategy that best suits your disk space constraints and internet connection speed.

Update (2025-04-22): Josh Triplett further shared on Reddit that Cargo will automatically manage its cache in the near future. Unused cached files will be automatically removed after a period of time through garbage collection.

Conclusion

Rust projects, especially their target directories, can consume considerable disk space, affecting local storage and backups; however, there are several effective countermeasures available. These include excluding target from backups, utilizing cargo clean, employing the more flexible cargo-clean-all, leveraging centralized build artifact management with CARGO_TARGET_DIR, and managing Cargo’s global cache. Understanding the balance between disk space savings and build performance allows developers to tailor their approach for an efficient and lean Rust development experience.

Subscribe to my RSS feed 📡 to stay up to date with my latest tutorials and tech articles.

Updated April 22, 2025. Originally published April 17, 2025

Share this Article