Skip to content

The Fast Build Toolchain in 2026: sccache, mold, nextest, and the Cost of Waiting

· 13 min read · default
developmentperformancerustbuild-toolsciproductivity

There is a well-known observation about waiting: under ten seconds, you stay focused; past a minute, you switch context and lose the thread. Build times sit precisely in that dangerous range. A ninety-second compile does not just cost ninety seconds — it costs the reload of everything you were holding in your head when you come back from the tab you switched to. Multiply by fifty builds a day across a team and slow builds stop being an inconvenience and start shaping behavior: bigger, riskier commits because iterating is expensive, less refactoring because the feedback loop punishes it, and tests skipped locally because they take too long.

The encouraging thing about build performance in 2026 is that the biggest wins are usually configuration, not engineering. You do not have to restructure your codebase; you install a few tools that address the three distinct phases where time actually goes. This guide covers those phases — compilation, linking, and testing — through sccache, mold, and cargo-nextest, plus bacon for the inner loop, and, importantly, how to measure whether any of it helped.

Measure before you optimize

The single most common mistake is optimizing the wrong phase. "The build is slow" is not a diagnosis — a build is at minimum compilation, linking, and (if you are running them) tests, and the balance between them varies enormously by project. A codebase with many small crates and a huge final binary may spend most of its time linking; one with heavy generics and macros may be dominated by compilation; a mature project may spend more time in tests than in either.

So start with a timing breakdown rather than a guess. In Rust, cargo build --timings produces an HTML report showing exactly how long each crate took and where parallelism stalled — often revealing that one dependency serializes everything else. For a coarser view, time cargo build on a clean tree versus an incremental one tells you how much you are paying for cold builds. And hyperfine gives you statistically sound before/after comparisons rather than the single noisy run that is easy to fool yourself with.

This matters because each tool below addresses one phase and does nothing for the others. Installing a faster linker when your bottleneck is compilation produces a rounding error and a false sense of progress.

Compilation: stop rebuilding what you already built

The most common source of waste in compilation is redundancy — building the same dependency crate, with the same flags, that you or a colleague or a CI runner already built an hour ago. sccache addresses this directly: it wraps the compiler, hashes the inputs, and returns a cached artifact when it has seen that exact compilation before. It supports Rust, C/C++, and CUDA.

What elevates sccache beyond a local cache is shared storage backends — S3, GCS, Redis, or GitHub Actions cache. That changes the economics of CI in particular. The default CI experience is a cold machine compiling every dependency from scratch on every run, which is pure waste since those dependencies have not changed. With a shared cache, the first run populates it and every subsequent run downloads instead of compiling. Teams commonly see CI build times fall by half or more, and the same cache serves developer machines.

Setup is a single environment variable (RUSTC_WRAPPER=sccache) or a ~/.cargo/config.toml entry. The important follow-up is verification: sccache --show-stats reports your hit rate, and a low hit rate is a signal that something in your inputs is varying — unstable RUSTFLAGS, absolute paths baked into output, or incremental compilation interfering. A cache with a poor hit rate is worse than none, because you pay the lookup cost for nothing.

Linking: the serialized tail

Linking is the phase people forget, and it is often the worst offender in the edit-compile-run loop. Here is why: compilation parallelizes beautifully across cores, but linking traditionally does not. You compile two hundred files across sixteen cores in twenty seconds, then wait eight seconds while one core links. In an incremental rebuild — where you changed one file and only that file recompiles — the link can be most of your wait.

mold is a drop-in linker designed from the start to use all available cores. It routinely cuts link times by an order of magnitude, and because the win lands squarely in the incremental rebuild path, it is the change developers feel most immediately. Adoption is genuinely trivial: mold -run cargo build wraps any build command with no configuration, or you add -fuse-ld=mold to your linker flags for a permanent setup.

The reason to combine mold with sccache is that they attack different halves of the same wait. sccache eliminates compilation you have done before; mold accelerates the link that remains and cannot be cached. Neither substitutes for the other, and together they typically produce a bigger improvement than either alone.

Testing: isolation and honest failures

The third phase is tests, and here cargo-nextest changes the model rather than just the speed. Standard cargo test runs all tests in a binary within one process; nextest runs each test in its own process. That yields several consequences beyond the typical 2–3x speedup.

Isolation becomes real. Tests cannot corrupt each other's global state, so an ordering-dependent failure surfaces immediately instead of appearing mysteriously months later. A crash is attributable — if a test segfaults or aborts, you learn which one, rather than losing the whole binary's results. Flaky tests are named as such: with --retries, a test that fails then passes is reported as FLAKY rather than quietly passing on rerun, which matters because a flaky test is a different problem from a failing one and hiding it is how suites rot. And CI sharding is built in via --partition, so splitting a suite across runners is a flag rather than a scripting project.

The main gap to know: nextest does not run doctests, so the common CI pattern is cargo nextest run && cargo test --doc.

The inner loop: don't run builds manually

The fastest build is the one you did not have to invoke. bacon runs in a side terminal, watches your source, and reruns cargo check, clippy, or tests on every save, showing a compact always-current error summary. The gain is not raw speed — it is that compilation overlaps with your thinking rather than blocking it, and you see the first error prominently instead of scrolling output.

This pairs naturally with the phase tools: bacon gives continuous feedback, sccache and mold make each of those background runs fast enough to finish before you have finished reading the previous error. For non-Rust projects, watchexec provides the same continuous-feedback pattern for any command.

Putting it together, and verifying

A complete setup is short:

cargo install sccache cargo-nextest bacon --locked
sudo apt install mold        # or brew install mold
# ~/.cargo/config.toml
[build]
rustc-wrapper = "sccache"

[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

Then verify each piece independently, because a silent misconfiguration is easy: sccache --show-stats should show a climbing hit rate; readelf -p .comment ./target/debug/yourbin | grep -i mold confirms mold actually linked it; cargo nextest run should visibly finish faster than cargo test. Measure the end-to-end result with hyperfine on a realistic change — touch one file, rebuild — rather than on a clean build, since incremental rebuilds are what you actually do all day.

In CI, add the shared cache backend (SCCACHE_GHA_ENABLED=true on GitHub Actions, or an S3 bucket) and use nextest's --partition to shard across runners. CI is where these tools pay off most dramatically, because CI machines are cold by default and repeat the same wasted work on every run.

Beyond configuration: what actually makes builds slow

If you have applied the phase tools and the loop is still painful, the remaining causes are structural — and worth understanding even if you decide not to act on them, because they explain why some projects resist optimization.

Dependency count and depth is the most common. Every crate you depend on must be compiled at least once, and a deep dependency graph serializes: crate C cannot start until B finishes, which waited on A. cargo build --timings shows this directly as a long critical path with idle cores. Auditing for dependencies you use trivially — pulling in a large crate for one helper function — is often the highest-leverage structural fix, and it reduces supply-chain surface at the same time.

Generic and macro-heavy code costs compile time in proportion to instantiation. A generic function used with twenty types is compiled twenty times, and heavy procedural macros run arbitrary code at compile time. Where a hot generic does not need to be generic, monomorphizing it manually or narrowing its bounds can measurably cut compile time. This is a real trade against ergonomics, so measure before contorting an API.

Crate granularity cuts both ways. One giant crate cannot parallelize internally and forces full recompiles for small edits; hundreds of tiny crates add per-crate overhead and a deeper dependency chain. The useful heuristic is to split along boundaries that change at different rates — stable foundational code in its own crate so edits to volatile code do not rebuild it.

Debug information and optimization settings are the cheapest structural lever. Full debug info is expensive to generate and link; debug = 1 (line tables only) is often enough for backtraces at a fraction of the cost. And for dependencies you never step through, opt-level overrides in a profile let you optimize your own code without paying to optimize everything.

None of these are configuration changes, which is why they belong after the tools. But when a project stays slow despite caching and a fast linker, the answer is almost always in this list.

Knowing when to stop

A closing caution: build optimization is itself a task with diminishing returns, and it is unusually good at feeling productive. Once your incremental rebuild is a few seconds, further tuning buys little, and the remaining levers get progressively more invasive — restructuring crate boundaries, cutting dependencies, reworking generics. Those can be worth doing, but they are engineering projects with real risk, not configuration changes.

The honest sequence is: measure first, apply the cheap phase-specific fixes (cache, linker, test runner), measure again, and stop when the loop no longer breaks your concentration. The goal was never a benchmark number — it was staying in flow long enough to finish the thought you were having when you hit save.

The bottom line

Slow builds change behavior, not just schedules, and the biggest fixes in 2026 are configuration rather than engineering. Diagnose which phase actually costs you — cargo build --timings and hyperfine beat intuition — then apply the tool that addresses it: sccache to stop recompiling what you or CI already built, mold to parallelize the serialized link that dominates incremental rebuilds, cargo-nextest for faster, isolated, flake-aware tests with built-in CI sharding, and bacon so compilation happens while you think instead of while you wait. Verify each one really engaged, apply the shared cache in CI where the waste is largest, and stop optimizing once the loop stops interrupting you.

References and Resources

Tools

Background and analysis

Related 1337skills cheatsheets