Skip to content

Guides

Faster builds while developing

Make compiling quicker while you work.

5 min read Edit this page

This page helps you wait less while you work on a Renox app. It explains why Rust builds take time, what rnx new already does about it, and a few extra things you can turn on.

Here is the short version. Rust has to turn your code and all the libraries it uses into a program before the first page appears. That first build is slow. After that, rnx serve only rebuilds your code when you change it. Templates, translations and files in public/ don't need a build at all: they reload by themselves. The settings below make both kinds of build quicker.

#In this guide

#Words you'll meet

WordWhat it means
compileTurn Rust source code into machine code the computer can run.
linkThe last step of a build: glue all the compiled pieces into one program file.
crateA Rust package. Your app is a crate; Renox and the libraries it uses are crates too.
dependencyA crate your app uses. Each one has to be compiled at least once.
incremental rebuildA rebuild after a small change, where only the changed crate is compiled again.
profileA set of build settings. dev is used while you develop; release for the server.
opt-levelHow hard the compiler works to make code fast. 0 builds quickly but runs slowly; 3 is the fastest code.
debug infoExtra data in the program that tells tools which line of source each piece came from.
featureA switch in Cargo.toml that turns an optional part of a crate on or off.

#What rnx new already does

Every app made by rnx new comes with two speed-ups in its Cargo.toml.

#Fast password hashing in dev builds

Argon2 and BLAKE2 are optimised in dev builds. The settings are [profile.dev.package.argon2] and [profile.dev.package.blake2], both with opt-level = 3. (Argon2 uses BLAKE2 inside, so both need it.)

Why? Argon2 turns passwords into hashes, and it is slow on purpose, to make guessing passwords hard. Without optimisation it becomes very slow. Every login in every test would pay for it. With these two lines, only these two crates are optimised, and the rest of the dev build stays quick to compile.

#Smaller debug info

Debug info is only line tables ([profile.dev] debug = "line-tables-only").

That means the program still knows the file and line number of each piece of code, so a crash report (a backtrace) still points to the right line. But the program file is much smaller. A smaller file links faster, and linking is the part of each rebuild you wait for.

Tip

Need a debugger that shows the values of your variables? Set debug = true for a while. It makes builds slower, so switch back when you're done.

Tip

rnx serve passes any extra arguments on to cargo build. rnx serve --release runs the optimised release build: slower to build, but the app runs as fast as on the server. Useful to see how fast a heavy page really is.

#A faster linker

Linking is most of the time of an incremental rebuild. A faster linker helps on every change.

On Linux, install mold (or lld), then add a file .cargo/config.toml to the app:

TOML
# Use clang to drive the link, and tell it to use mold (Linux, 64-bit Intel/AMD)
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

This tells Rust: "when you build for 64-bit Linux, use clang to link, and let it call mold".

On other systems:

  • macOS: the default linker (ld-prime) is already fast. Nothing to do.
  • Windows: use rust-lld. Add rustflags = ["-C", "link-arg=-fuse-ld=lld"] for the target x86_64-pc-windows-msvc.

#Fewer dependencies

Less code to compile means faster builds. Renox has some parts you can switch off with features.

Renox's default features (the ones that are on unless you say otherwise) are:

FeatureWhat it gives you
fakeThe renox::fake re-export, used by factories to make fake test data.
httpReal web requests for state.http. It brings in the reqwest crate. The test fake works without it.
server-eventsanalytics::ServerEvent. It needs http.

An app that uses none of them can turn them off:

TOML
renox = { version = "…", default-features = false }
# or keep some: default-features = false, features = ["fake"]

Keep the rest of your renox line as rnx new wrote it: version = "…" when rnx came from crates.io, git = "…", rev = "…" when it came from Git. Only add the default-features and features parts.

default-features = false turns all three off. The comment shows how to keep only the ones you want: list them in features.

Some features are off unless you turn them on: postgres, s3, uuid and xlsx (Excel exports of data grids).

Note

s3 is the heaviest. It turns on object_store's aws feature, which brings in reqwest and aws-lc-rs (a crypto library written in C). It is not the AWS SDK.

For secure connections (TLS), Renox uses rustls with the ring provider. So no C crypto library (aws-lc) is compiled. The only C code that gets compiled is SQLite's.

#Sharing compiled dependencies

Do you have several Rust apps on one machine? They can share compiled dependencies with sccache. Turn it on by setting the environment variable RUSTC_WRAPPER=sccache.

sccache keeps a copy of everything it compiles. When the first build of another app needs the same crate, it reuses that copy instead of compiling it again.

#Docker

The Dockerfile that rnx make:deploy writes builds your dependencies in their own layer (a saved step of a Docker build), using a tool called cargo-chef.

Docker reuses that layer until Cargo.toml or Cargo.lock change. So after a change to your code, only your own crate is compiled, not every dependency again.

Important

Commit Cargo.lock to your repository. It records the exact version of every dependency, so the dependency layer is built from the same versions each time.