An excerpt from the repository, fetched Nov 13, 2026.
Read the project’s introduction
Minimizing Rust Binary Size
This repository demonstrates how to minimize the size of a Rust binary.
By default, Rust optimizes for execution speed, compilation speed, and ease of debugging
rather than binary size, since for the vast majority of applications this is ideal. But
for situations where a developer wants to optimize for binary size instead, Rust provides
mechanisms to accomplish this.
Build in Release Mode
By default, cargo build builds the Rust binary in debug mode. Debug mode disables many
optimizations, which helps debuggers (and IDEs that run them) provide a better debugging
experience. Debug binaries can be 30% or more larger than release binaries.
To minimize binary size, build in release mode:
$ cargo build --release
strip Symbols from Binary
By default on Linux and macOS, symbol information is included in the compiled .elf file. This
information is not needed to properly execute the binary.
Cargo can be configured to
automatically strip binaries.
Modify Cargo.toml in this way:
[profile.release]
strip = true # Automatically strip symbols from the binary.
Prior to Rust 1.59, run strip directly on
the .elf file instead:
$ strip target/release/min-sized-rust
Optimize For Size
Cargo defaults its optimization level to 3 for release builds,
which optimizes the binary for speed. To instruct Cargo to optimize for minimal binary
size, use the z optimization level in
Cargo.toml:
[profile.release]
opt-level = "z" # Optimize for size.
NoteIn some cases the "s" level may result in a smaller binary than "z", as explained in the
opt-level documentation:
It is recommended to experiment with different levels to find the right balance for your project.
There may be surprising results, such as ... the "s" and "z" levels not being necessarily
smaller.
Enable Link Time Optimization (LTO)
By default,
Cargo instructs compilation units to be compiled and optimized in isolation.
LTO instructs the linker to optimize at the
link stage. This can, for example, remove dead code and often times reduces binary size.
Enable LTO in Cargo.toml:
[profile.release]
lto = true
Dynamic Linking: Why It Doesn't Work
Some might suggest using prefer-dynamic for smaller binaries, but this approach has critical limitations:
No stable ABI - binaries break between Rust versions
Deployment complexity - requires exact library matches
Community consensus - static linking preferred for reliability
Reduce Parallel Code Generation Units to Increase Optimization
By default, Cargo specifies 16 parallel codegen units for release builds.
This improves compile times, but prevents some optimizations.
Set this to 1 in Cargo.toml to allow for maximum size reduction optimizations:
[profile.release]
codegen-units = 1
Abort on Panic
ImportantUp to this point, the features discussed to reduce binary size did not have an
impact on the behaviour of the program (only its execution speed). This feature does
have an impact on behavior.
By default, when Rust code encounters a situation when it must call panic!(),
it unwinds the stack and produces a helpful backtrace. The unwinding code, however, does require
extra binary size. rustc can be instructed to abort immediately rather than unwind, which
removes the need for this extra unwinding code.
Enable this in Cargo.toml:
[profile.release]
panic = "abort"
Remove Location Details
By default, Rust includes file, line, and column information for panic!() and [track_caller]
to provide more useful traceback information. This information requires space in the binary and
thus increases the size of the compiled binaries.
To remove this file, line, and column information, use the unstable
rustc -Zlocation-detail
flag:
$ RUSTFLAGS="-Zlocation-detail=none" cargo +nightly build --release
Remove fmt::Debug
With the
-Zfmt-debug flag
you can turn #[derive(Debug)]and
{:?} formatting into no-ops. This
will ruin output of dbg!(), assert!(), unwrap(), etc., and may break code that unwisely
relies on the debug formatting, but it will remove derived fmt functions and their strings.
$ RUSTFLAGS="-Zfmt-debug=none" cargo +nightly build --release
Optimize libstd with build-std
NoteSee also Xargo, the predecessor to build-std.
Xargo is currently in maintenance status.
NoteExample project is located in the build_std folder.
Rust ships pre-built copies of the standard library (libstd) with its toolchains. This means
that developers don't need to build libstd every time they build their applications. libstd
is statically linked into the binary instead.
While this is very convenient there are several drawbacks if a developer is trying to
aggressively optimize for size.
The prebuilt libstd is optimized for speed, not size.
It's not possible to remove portions of libstd that are not used in a particular application
(e.g. LTO and panic behaviour).
This is where build-std
comes in. The build-std feature is able to compile libstd with your application from the
source. It does this with the rust-src component that rustup conveniently provides.
Install the appropriate toolchain and the rust-src component:
$ rustup toolchain install nightly
$ rustup component add rust-src --toolchain nightly
Build using build-std:
# Find your host's target triple.
$ rustc -vV
...
host: x86_64-apple-darwin
# Use that target triple when building with build-std.
# Add the =std,panic_abort to the option to make panic = "abort" Cargo.toml option work.
# See: https://github.com/rust-lang/wg-cargo-std-aware/issues/56
$ RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none" cargo +nightly build \
-Z build-std=std,panic_abort \
-Z build-std-features="optimize_for_size" \
--target x86_64-apple-darwin --release
The optimize_for_size flag provides a hint to libstd that it should try to use algorithms
optimized for binary size. More information about it can be found in the
tracking issue.
On macOS, the final stripped binary size is reduced to 51KB.
Remove panic String Formatting with panic=immediate-abort
Even if panic = "abort" i
Read the full README on GitHub