🎓 A1Learner
🔍
0 of 149 lessons completed

Rust Release Profiles

A profile is a named set of build settings. One profile builds quickly while work continues, and one profile builds a fast program for shipping.

The dev profile, written out

Plain cargo build and cargo run use this profile. opt-level 0 means no optimisation work, so rebuilds stay quick:

[profile.dev]
opt-level = 0

The release profile, written out

cargo build --release uses this one. opt-level 3 is the most optimisation — and where dev keeps debug info, release bakes none in:

[profile.release]
opt-level = 3
debug = false

Change one setting in one profile

Write only the setting you change — the rest keep their defaults, and the other profile is not touched:

[profile.dev]
opt-level = 1

Overflow checks follow the profile

add_one adds 1 to a u8. And 255 is one too many. A dev build panics with "attempt to add with overflow". A release build quietly prints 0. (overflow-checks = true in [profile.release] makes the shipped build panic too.)

fn add_one(n: u8) -> u8 { n + 1 }
let big = add_one(255);
println!("{big}");

The small-binary recipe, in one place

All five size settings together — the combination measured at 282,248 bytes above:

[profile.release]
opt-level = "z"
lto = "fat"
codegen-units = 1
panic = "abort"
strip = "symbols"

Make your own profile

inherits copies a whole profile, then your lines change it. Build it with cargo build --profile fast — the output lands in target/fast:

[profile.fast]
inherits = "release"
opt-level = 2

One package, one setting

This table lifts helperlib to full speed inside the slow dev profile. Nothing else changes:

[profile.dev.package.helperlib]
opt-level = 3

Proof from a verbose build

cargo build -v prints every compiler call. In a dev build, only helperlib's line carries -C opt-level=3. The other lines carry no opt-level at all, which is the dev default of 0:

cargo build -v

Libraries one way, your code another

The profile sets your own code to 1. The "*" override lifts every library to 2 in the same build:

[profile.release]
opt-level = 1

[profile.release.package."*"]
opt-level = 2

Build-time code has its own override

Build scripts and procedural macros run while your project builds. build-override retunes those, and only those:

[profile.dev.build-override]
opt-level = 3

Example

// Cargo.toml
// [profile.dev]
// opt-level = 0
// [profile.release]
// opt-level = 3
fn main() {
    let dev = 0;
    let release = 3;
    println!("dev opt-level = {dev}: builds fast, runs slower");
    println!("release opt-level = {release}: builds slower, runs fast");
}

Key points

  • Cargo has four built-in profiles, not two.
  • They are dev and release.
  • There is also test, for cargo test.
  • It inherits dev.
  • And there is bench, for cargo bench.
  • It inherits release.
  • codegen-units splits one crate into pieces.
  • The compiler can work on the pieces in parallel.
  • The release default is 16.
  • Fewer pieces can mean a faster program.
  • But they also mean a slower build.
  • panic = "abort" makes a panic kill the program at once.
  • The panic does not unwind the stack.
  • The binary gets smaller.
  • On Linux, the process ends with an abort.
  • Its exit code is 134, instead of the usual 101.
  • Profile settings only count in the workspace root's Cargo.toml: a [profile] section inside a library you depend on is ignored
  • In a test build of a small program for this course, the full small-binary recipe shrank the binary a lot.
  • The recipe is opt-level "z", fat LTO, codegen-units 1, panic "abort", and strip "symbols" together.
  • The binary shrank from 444,824 bytes to 282,248 bytes.
  • strip alone reached 336,824 bytes.
  • A profile is one named bundle of settings for one kind of build.
  • Profile override: a [profile.<profile>.package.<name>] table that changes build settings for one package inside a profile
  • Use a package override in two cases.
  • It can speed up one slow library in dev builds.
  • Or it can shrink one library in release builds
  • The "*" name overrides every non-member package at once.
  • A named package beats "*" for that package
  • Use [profile.<profile>.build-override] to retune build scripts and procedural macros, which run on the build machine
  • A library cannot set its own override inside your project

In this example, the comments show the defaults written out as Cargo.toml sections. In a real project, you write a section like this only to change a value. dev is 0 and release is 3. The two println! lines print the trade-off in plain words. The dev profile spends no time optimising, so builds stay quick while you work. The release profile spends the most time optimising, so the one build you ship runs as fast as it can.

Note

There is no separate config file to hunt for.

The profiles live in the same Cargo.toml as your dependencies.

So the build you ship is described next to the code it builds.

Your own crates are workspace members, and overrides skip members.

To retune your own crate, change the profile itself.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

One library makes your dev builds feel slow, because it always builds unoptimised. The clean fix is: