Rust release builds are tuned for speed. A few lines in Cargo.toml shift that toward size. This post covers the settings that matter, what the official docs say about each, and what each one costs.
Start with a baseline
The Cargo profiles reference lists the release defaults: opt-level = 3, lto = false, panic = 'unwind', strip = "none" and codegen-units = 16. Build once with these and write down the binary size. Keep each size change only if it improves the tradeoff you care about: measured size, runtime speed, build time and diagnostics.
Strip symbols
strip = true is the same as strip = "symbols". The rustc codegen options say this removes debuginfo and the rest of the symbol table at link time. The min-sized-rust guide says symbol information is included by default on Linux and macOS and is not needed to run the binary. The cost is less information when you inspect the binary. The rustc docs also say stripping cannot be relied on as a security or obfuscation measure.
Optimize for size
Cargo documents opt-level = "s" as optimizing for binary size. "z" does the same and also turns off loop vectorization. Neither is guaranteed to win. The Cargo reference says the "s" and "z" levels are not necessarily smaller, and the rustc docs say z often produces larger binaries than s. Try both and compare.
Link-time optimization
lto = true runs fat LTO, which optimizes across all crates in the dependency graph. The min-sized-rust guide says this can remove dead code and often reduces size. "thin" takes substantially less time to run. The cost of either is a longer link step.
Fewer codegen units
Release builds default to 16 codegen units. The guide says this speeds up compilation but prevents some optimizations. Setting codegen-units = 1 gives the optimizer the whole crate at once, at the price of slower builds.
Abort on panic
panic = "abort" ends the process on panic instead of unwinding the stack. The guide says this removes unwinding code but changes program behavior. The Cargo reference notes that tests, benchmarks, build scripts and proc macros ignore the setting. The rustc docs add that if any crate in the graph uses abort, the final binary must use it too.
Tools beyond the profile
cargo-bloat shows what takes the most space in an executable. The guide also describes UPX, a language-agnostic packing tool that, per the guide, claims to typically reduce binary size by 50-70 percent, though the actual result depends on the executable. It also describes build-std, which rebuilds the standard library with your flags.
What to do
- Build in release mode and record the binary size.
- Start with this profile and rebuild:
[profile.release]
strip = true
opt-level = "z"
lto = true
codegen-units = 1
- Change
opt-levelto"s"and compare the two sizes. Keep the smaller one. - If you consider
panic = "abort", check whether callers or recovery paths rely on unwinding. Add it only after that review, then exercise the built release program’s panic behavior; Cargo’s normal tests ignore this profile setting. - If the binary is still large, run
cargo-bloatto see which functions and crates take the space. - Keep a debug-capable build if you need readable backtraces from stripped release binaries.

The Campfire
No commentsNobody has pulled up a log by this one yet. Be the first to say what you make of it.
Held for the desk. It appears after a look.