Rust 1.24 enables incremental compilation for development builds
Stable incremental compilation reuses compiler work across edits. Its usefulness depends on dependency tracking, retained artifacts, and the kind of build a developer requests.
Editing one function should not require repeating every unchanged part of a compilation. Making that expectation correct is difficult because a small textual edit can affect code far beyond the edited line. Rust 1.24 brings incremental compilation to stable Rust and enables it by default for Cargo's development profiles. The compiler can retain useful results between builds while determining which results a change invalidates.
Today's release announcement also makes a rustfmt preview available through rustup. The compiler change is the larger alteration to the edit-and-build workflow. It lets the implementation exploit the fact that successive versions of a program usually share substantial work, without requiring a developer to divide the source according to a private manual caching scheme.
I find this valuable because strong static checks are easier to use when feedback arrives during the work they inform. Rust's semantics appeal to me, and reducing repeated compiler work makes those semantics more practical during iteration. That judgment does not require assuming every edit has the same rebuild cost.
Reuse needs a dependency explanation
Michael Woerister's 2016 description of incremental compilation explains the underlying problem. A compilation consists of smaller computations whose results depend on source inputs and other intermediate results. Saving an intermediate result is useful only if the next compilation can establish that its inputs remain suitable.
The design uses a dependency graph to record those relationships. A result can remain reusable when the inputs that influenced it have not changed. If an input changes, dependent results require reconsideration. The hard part is identifying those relationships accurately enough to avoid both invalid reuse and unnecessary recomputation.
This is why counting edited lines is a poor description of expected speed. A change to a widely used definition can affect many results. A longer change confined to one computation can have fewer consequences. The relevant property is the dependency relationship, rather than the visible size of the patch.
The earlier article describes the architecture during its alpha phase. Its historical implementation details and measurements should not be treated as a complete specification of the stable release. They explain the approach that led here. Today's stable announcement establishes that the feature has progressed to ordinary use through Cargo.
An arithmetic analogy helps isolate the reuse decision. If a computation adds one input to a product of two others, changing only the added input does not require recomputing the product. Changing an input to the product does. A compiler has many more results and dependencies, but it must answer the same question about which saved work still follows from the current inputs.
The analogy also shows the correctness requirement. Reusing an obsolete product makes the final answer wrong even if the last addition is correct. A stale intermediate compiler result can similarly invalidate everything built on it. Reuse is an optimization only when it preserves the meaning of the current program.
There are two distinct failure modes in dependency tracking. Missing a dependency can permit incorrect reuse, while recording a relationship that is too broad can make the compiler repeat valid work. The first threatens correctness and the second limits the benefit. An incremental compiler must protect the first property while improving the precision needed for the second.
This also explains why architecture work precedes a useful default. Caching an arbitrary intermediate file is easy compared with knowing which source facts justify keeping it. A compiler needs that explanation across its own processing stages. The stable feature represents integration of reuse into the compilation process, rather than a wrapper that simply skips a command because its input filenames look unchanged.
Cargo selects the development default
The Cargo change enabling the default is precise about its scope. It enables incremental compilation for development-related profiles, excluding release and benchmark builds from that default. Saying the feature is simply on for all builds would lose an important distinction in the implementation.
That scope matches the workflow the feature targets. Development involves a sequence of related compilations as code changes. A release build has a different purpose and can make different choices about optimization. A benchmark build also needs to represent the program configuration whose performance is being investigated, so its relationship to development settings deserves explicit attention.
The change supplies configuration controls as well. A project can disable incrementality for its development profile through its manifest. The following is a configuration example, rather than a recommendation to turn off the new default.
[profile.dev]
incremental = false
It records a project-level choice. The implementation discussion also identifies Cargo configuration and the CARGO_INCREMENTAL environment variable as controls with different scopes. Those controls are useful because a default must coexist with cases where a maintainer needs to isolate an implementation problem or compare behavior.
The setting belongs in the explanation of a build result. Two invocations compiling the same source can have different reuse opportunities if they run under different profiles or incremental settings. Reporting the command and its configuration is more informative than labeling a build only as fast or slow.
A cache helps a sequence of builds
The first compilation has no earlier result to reuse. It has to produce the relevant output and any information needed for subsequent reuse. A later build can benefit from retaining that information. Comparing those operations as though they were identical would obscure the intended advantage.
The same reasoning explains why removing build artifacts changes the experiment. A clean build answers how much work the toolchain performs from an empty state. An incremental rebuild answers how much it can retain after a particular edit. Both can matter, but they describe different activities in a development process.
This has an implication for automated environments. A worker that starts with no retained compilation state cannot benefit from a previous worker's incremental results merely because the feature is enabled. Availability of the saved work is a prerequisite for reusing it. That is an inference about the cache model, not a claim that this release provides a particular distributed-cache service.
The useful effect is therefore a change in repeated local work, with possible benefits elsewhere when the required state exists. It should not be reduced to a universal speedup percentage. Program structure and the edit determine how much of the earlier computation remains relevant.
Code generation has another tradeoff
Rust 1.24 separately changes the default number of code-generation units. The implementation change sets the release-mode default to 16. The announcement describes a tradeoff between faster builds and some loss of execution performance, and identifies a single unit as an option when maximum execution speed is required.
That change should not be folded into the incremental-compilation story as though both were one switch. Dividing code generation changes the units in which the compiler processes and optimizes output. Incremental compilation concerns retaining work across compilations. They can both affect build time while answering different questions.
For a release profile, the documented single-unit configuration is straightforward.
[profile.release]
codegen-units = 1
The setting states an optimization preference for that profile. It does not enable incremental compilation or provide evidence that any particular application becomes faster. Understanding which mechanism a setting controls is necessary to interpret its effect on a build.
I prefer separate controls for these choices because build latency and program execution are different costs. A development profile can prioritize feedback while a release profile expresses a different objective. The configuration makes the distinction visible to the next maintainer.
Rustfmt arrives with a preview contract
The formatting tool has its own stability boundary. Rust 1.24 distributes rustfmt-preview through rustup component add, and the announcement says it has not yet reached version 1.0. This makes it available alongside a stable compiler without claiming that all formatter behavior is settled.
Automatic formatting and incremental compilation reduce different kinds of repeated work. Formatting applies a common layout mechanically. Incremental compilation avoids repeating valid compiler results. Neither removes the need to decide whether the program expresses the right behavior, but both can reduce the effort surrounding that decision.
The release also reminds users that cargo check remains available for fast compiler feedback. Producing a runnable artifact and checking source are different requests. When the immediate question concerns type correctness, requesting less output can be appropriate. When the question requires executing the program, incremental compilation makes repeated artifact production more economical by reusing the work that still applies after the edit.