One of the most common frustrations when working with rust is the speed of the compiler. Well actually, the lack of it. At work we currently have a 15k line project. From a clean slate, compilation takes 92 seconds. If all of the dependencies are compiled, cargo build still takes over 30 seconds to run on a two year old 15 inch MacBook Pro. Anecdotally, this is more than enough time to get distracted with Slack or Reddit. Time to dive into the less common features of cargo.

By design the usual collection of cargo commands do not expose the underlying details of the compiler. This choice removes a large swath of potential build system issues. But sometimes users need to fiddle with the lower levels of the toolchain. cargo provides a subcommand specifically for this purpose - cargo rustc.

λ> cargo rustc –help
Compile a package and all of its dependencies

In our project we have three targets: a library which contains all of the business logic, an executable which wraps the library and provides the command line parsing, and a second executable for managing our rfc process.1 With the normal subcommands - build, test, doc - cargo automatically handles creating all three build artifacts. This is one of the advantages of severely limiting users ability to customize the build process. cargo rustc does not provide this affordance, instead requiring you to specify the desired build artifact with --lib or --bin. The documentation quoted below explains exactly how to use these flags.

This command requires that only one target is being compiled. If more than one
target is available for the current package the filters of –lib, –bin, etc,
must be used to select which target is compiled. To pass flags to all compiler
processes spawned by Cargo, use the $RUSTFLAGS environment variable or the
`build.rustflags` configuration option.

Reading through github issues, reddit, and the internals forum there is oft mention of the -Z flag to rustc. According to the help page -Z is the catch-all flag to”Print internal options for debugging rustc”. Let’s see what is available.

λ> cargo rustc –lib -Zhelp
warning: the option `Z` is unstable and should only be used on the nightly compiler, but it is currently accepted for backwards compatibility; this will soon change, see issue #31847 for more details

Oops. rustup to the rescue. Adding the +nightly flag to our command will tell cargo to use the nightly toolchain and thus remove the warning. The command to run is now cargo +nightly rustc --lib -Zhelp

Three of the debug options have reduced the wait for feedback: parse-only, no-trans, and incremental.2 The first two options skip compilation phases while incremental caches intermediate compilation results and only reruns the phases invalidated by subsequent changes.

parse-only does exactly what it says on the tin - only runs the parse phase of the compiler. Only running parsing catches typos and some type errors and completes in under a second. This behavior serves as the basis for many editor plugins such as flycheck.

no-trans runs all of the compilation phases up until llvm. This this covers linting and analysis which produce the bulk of the valuable feedback provided by the compiler. Including unimplemented methods for traits, missing match branches, move semantics, and lifetime checks. I have noticed myself running this command most frequently. Looking through my shell history confirms that notion.

Incremental compilation is a very new and experimental feature in rustc. There are many caveats with using incremental compilation, not the least of which is no guarantees on correctness. The goal of this exercise is to document some options for gaining information quickly about your rust program. Not to reduce overall compilation times. Incremental compilation is enabled by the incremental parameter to the -Z flag. incremental also requires a path to store the incremental build index. So the final command for experimenting with incremental compilation is cargo +nightly rustc --lib -Zincremental=incremental_state. With incremental compilation we have seen build times reduce to 4 seconds for minor changes to a single module.

tl;dr cargo +nightly rustc --lib -Zno-trans is awesome.3

  1. We follow a similar rfc process to rust for proposing changes to the project. So far we have found the separation of design and implementation to significantly reduce change review time (even when adding together rfc and implementation reviews). This probably deserves its own post.
  2. When reading through the help listing I was reminded of no-analysis which is invaluable in debugging macros but completely unrelated to this post.
  3. You can automatically run this command with cargo +nightly watch -- "rustc --lib -- -Zno-trans". The watch subcommand can be found on github.