The compile-time tax
You change one line in main.rs, hit cargo build, and watch your CPU fans spin up while the compiler chews through 200 dependencies. You didn't touch any of them. They haven't changed in weeks. Yet here we are again, recompiling serde_derive for the fifth time today.
This is the price of Rust's design. Generics get monomorphised, traits get specialised, LLVM optimises everything aggressively. The result is fast code, slow builds. Cargo's incremental cache helps for a single project, but it doesn't share anything across projects, and it doesn't survive a cargo clean or a target/ wipe.
sccache fixes that. It's a wrapper around rustc that stores compiled artefacts in a cache keyed by the source plus the compiler flags. The next time rustc is asked to build the exact same crate with the exact same inputs, sccache hands back the cached object file instead of running the compiler.
How it actually works
When you set RUSTC_WRAPPER=sccache, Cargo doesn't call rustc directly. It calls sccache rustc <args>. Before doing anything expensive, sccache hashes the inputs: source files, the compiler version, the flags, environment variables it considers relevant. That hash becomes a cache key. If the key already exists in the cache (locally on disk by default, or in S3/Redis/GCS if you configured that), sccache copies the object file out and returns immediately. If not, it runs the real compiler, captures the output, and stores it under the key for next time.
The win is biggest the second time you build. The first build still has to compile everything, but every clone of the same project, every CI run, every coworker who pulls the same lockfile gets to skip nearly all of the work.
Installing it
The recommended way is via cargo install, but a pre-built binary from the GitHub release page is faster and avoids needing to compile sccache itself.
# Easiest path: install with cargo (compiles sccache from source).
# Takes a few minutes the first time.
cargo install sccache --locked
# Or grab a pre-built binary. Replace VERSION with the latest release tag.
curl -L https://github.com/mozilla/sccache/releases/download/vVERSION/sccache-vVERSION-x86_64-apple-darwin.tar.gz \
| tar xz -C /usr/local/bin --strip-components=1
# Sanity-check.
sccache --version
On macOS, brew install sccache works too. On Linux, your package manager probably has it. The version doesn't matter much as long as it's recent enough to know about your rustc version's hash inputs.
Turning it on for one build
The simplest possible usage is one environment variable.
# Tell Cargo to invoke rustc through sccache instead of directly.
export RUSTC_WRAPPER=sccache
# Build. The first run is the same speed as without sccache (cache miss
# on every crate). The second run is dramatically faster.
cargo build
# Show cache hit/miss stats since the daemon started.
sccache --show-stats
After a build, sccache --show-stats reports something like:
Compile requests 412
Compile requests executed 412
Cache hits 389
Cache hits (Rust) 389
Cache misses 23
Cache misses (Rust) 23
Cache timeouts 0
Cache read errors 0
Forced recaches 0
Cache write errors 0
Compilation failures 0
Cache errors 0
Non-cacheable compilations 0
Non-cacheable calls 0
Non-compilation calls 0
Unsupported compiler calls 0
Average cache write 0.012 s
Average cache read miss 0.000 s
Average cache read hit 0.005 s
Each "cache hit" is a crate that didn't have to be compiled from scratch. On a clean build of a workspace with 300 dependencies, hit rates of 90%+ are normal once your cache is warm.
Making it permanent
Setting RUSTC_WRAPPER per-shell works but gets old. The cleanest fix is to put it in Cargo's config so it's picked up automatically.
# In ~/.cargo/config.toml (applies to every project on this machine).
[build]
rustc-wrapper = "/usr/local/bin/sccache"
Use the full path. Cargo doesn't search PATH for this setting on every platform, and a relative name occasionally fails silently in CI.
If you only want sccache for one project, drop the same block in <project>/.cargo/config.toml instead. Project-local settings always win over the global one.
What sccache caches and what it doesn't
sccache only caches rustc calls. It does not cache:
- Build scripts (
build.rs). These are arbitrary code and their outputs depend on the environment, so caching them safely is hard. - Procedural macro expansion. The output is part of the crate that uses the macro, which sccache does cache, but the macro itself is recompiled if its source changes.
- Linking. The final link step is fast anyway, and the inputs change every time you touch your top crate.
It also won't help if your crate has incremental compilation turned off (some release profiles do this) or if Cargo decides it has to rebuild for unrelated reasons (a different feature flag set, a different target triple, a build script wrote a different file). Watch for "Non-cacheable" entries in the stats. They tell you which crates are skipping the cache and why.
Sharing a cache across machines
Local cache is great for one developer. The bigger win comes from sharing.
# ~/.cargo/config.toml plus a shared backend, configured via env vars.
[build]
rustc-wrapper = "/usr/local/bin/sccache"
# Point sccache at an S3 bucket. Every machine that exports these vars
# shares the same cache.
export SCCACHE_BUCKET=our-rust-cache
export SCCACHE_REGION=eu-west-1
export SCCACHE_S3_KEY_PREFIX=v1/
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
Now CI builds populate the cache, and your laptop reads from it. The first time a developer clones the repo, the build pulls cached artefacts down instead of compiling from source. CI runs that share dependencies (PR builds, scheduled builds, release builds) all reuse the same objects.
For self-hosted setups, sccache also supports Redis, Memcached, and a plain HTTP backend. Pick whatever your team already runs.
When sccache doesn't help
A few situations where it'll disappoint you:
- Heavy use of build scripts. If your dependency graph is full of crates that codegen at build time (
prost,tonic,bindgenagainst headers that change), sccache's hit rate drops. - Single-crate projects you build once. The cache pays off on the second and tenth build, not the first.
- Workspaces with many feature flag combinations. Each feature set is a different cache key, so if you build with
--features aand then--features b, they cache separately.
For those cases, look at narrower fixes: smaller dependency trees, cargo-chef for Docker builds, or simply staying on incremental compilation in target/.