Profiles & Build Settings
Cargo profiles control optimization, debug info, panic strategy, and link-time optimization. Tuning them is how you trade compile time for runtime speed and binary size.
Recipe
[profile.dev]
opt-level = 0
debug = true
[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
strip = "symbols"When to reach for this: Shipping production binaries, CI release builds, or shrinking deploy artifacts.
Working Example
Release profile for an Axum API service:
[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
panic = "abort"
strip = "symbols"
[profile.release-fast]
inherits = "release"
lto = false
codegen-units = 16cargo build --release
cargo build --profile release-fastWhat this demonstrates:
opt-level = 3maximizes runtime performance- Thin LTO balances link time and cross-crate inlining
codegen-units = 1improves optimization at cost of compile time- Custom profiles inherit from
releaseordev
Deep Dive
Common Profile Keys
| Key | Dev typical | Release typical |
|---|---|---|
opt-level | 0 | 3 or s for size |
debug | true | false or limited |
lto | false | thin or fat |
codegen-units | 256 | 1 for max perf |
panic | unwind | abort for smaller binaries |
strip | none | symbols or debuginfo |
Size-Oriented Release
[profile.release-size]
inherits = "release"
opt-level = "z"
lto = true
panic = "abort"
strip = "symbols"opt-level = "s"or"z"favors smaller binaries- Fat LTO can shrink further but links slower
- Pair with
cargo bloatto find large symbols
RUSTFLAGS and Target CPU
RUSTFLAGS="-C target-cpu=native" cargo build --releasetarget-cpu=nativeuses host CPU features (not portable)- Set in
.cargo/config.tomlfor team-wide defaults - Document portability requirements when using native tuning
Gotchas
- Profiling debug builds - hot paths look nothing like release. Fix: benchmark with
--releasealways. - Fat LTO in CI - link step timeouts. Fix: use
thinin CI, fat only for final release artifacts. panic = "abort"without testing - unwinding-basedDropcleanup assumptions break. Fix: verify FFI and scoped guards still behave correctly.- Inherited profile typos -
inheritsmust name an existing profile. Fix:cargo build --profile foolocally before CI. - Strip too aggressive - you lose useful backtraces in prod. Fix: keep debug symbols in a separate file or use
strip = "debuginfo"only.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
opt-level = 2 | Faster compile, near-release perf | Last-mile latency tuning |
cargo bloat --release | Find size regressions | Proving algorithmic improvements |
sccache / mold | CI compile/link speed | Replacing sound profile choices |
FAQs
What is the difference between thin and fat LTO?
Thin LTO optimizes across crates with faster links. Fat LTO is more aggressive and slower, often yielding smaller or faster binaries.
Should I set codegen-units to 1?
For final release binaries where compile time is acceptable, yes. For dev iteration, keep defaults.
How do I speed up dev builds?
Use cargo check, split workspace crates, disable unused default features, and consider opt-level = 1 in a custom dev-fast profile.
What does strip = symbols do?
Removes symbol tables from the binary, reducing size. Keep separate debug info if you need symbolicated crash reports.
Can dependencies have different profiles?
[profile.release.package.foo] overrides settings per dependency crate (e.g. always optimize a hot path dep).
How does panic = abort affect Result?
Result still works; only panics abort the process instead of unwinding. catch_unwind no longer catches panics.
What is incremental compilation?
Enabled in dev by default. Release builds typically disable it for deterministic optimized output.
How do I benchmark profile changes?
Use Criterion with the same inputs across profiles; compare wall time and binary size with ls -la target/release/foo.
Does LTO affect compile-time features?
LTO can inline across crate boundaries, exposing optimizations but also longer link steps and higher memory use during build.
Where do I set rustflags for the whole workspace?
.cargo/config.toml under [build] rustflags = [...] applies to all workspace members unless overridden.
Related
- Cargo Basics - build commands
- Reducing Binary Size - size tactics
- Release Tuning - performance focus
- Useful Cargo Subcommands -
cargo bloat
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.