Making Rust binaries smaller by default
Rust’s “hello world” binaries have long been criticized for being several megabytes in size, which many see as undermining its “zero-cost abstractions” reputation and deterring developers used to tiny C executables. A recent change to Cargo will strip standard-library debug symbols by default when no debuginfo is requested, cutting typical release binaries by roughly 90% (to a few hundred kilobytes) while keeping full symbols available for development builds. Commenters debate how much binary size matters on modern systems versus embedded and constrained environments, and weigh this change against trade‑offs around backtraces, panic behavior, link-time optimization, and Rust’s static-linking, no-stable-ABI design.
First impressions & binary size as a signal
- Several commenters say a multi‑MB “hello world” can turn evaluators off immediately, especially C/C++ developers used to tiny binaries.
- Others argue that initial size is mostly a fixed cost and not predictive of marginal growth as programs get larger.
- There’s debate over whether “hello world size” is a good proxy for abstraction cost: some think yes in general, others note Rust’s 4MB→400KB jump (via debug stripping) shows size can be dominated by tooling choices, not language overhead.
What’s in a ~415KB Rust “hello world”?
- Major contributors cited:
- Backtrace support: stack walking, DWARF parsing, name demangling, path handling, compressed ELF sections.
- I/O machinery: buffered stdout, synchronization, allocator, vector implementation.
- Formatting, especially floats, which rely on substantial data tables and corner‑case handling.
- Static linking of std (for portability and lack of a stable Rust ABI) inherently pulls in more than a dynamically linked C program.
Stripping debug info & stack traces
- Prior behavior: release binaries still embedded std’s debug info, bloating “hello world” to ~4MB.
- New default:
strip = "debuginfo"when no debuginfo is requested, removing std’s DWARF but preserving runtime behavior. - Consequence: release backtraces lose line numbers, but commenters note these were rarely useful without debug info for user code.
- Some worry about losing symbols; others emphasize external/split debug info and debuginfod as the right long‑term pattern.
Manual size tuning & tradeoffs
- Common recipe:
opt-level="z",lto=true,codegen-units=1,panic="abort", plus building std with panic_abort / panic_immediate_abort and stripping. This can reach tens of KB. - Tradeoffs discussed:
- Slower compilation and sometimes slower runtime for size‑optimized builds.
panic="abort"removes unwinding, destructors-on-panic, and some recovery patterns; likened to-fno-exceptions.- Stripping everything (not just debuginfo) can break binaries;
--strip-unneededis highlighted.
When binary size matters (and when it doesn’t)
- Some say anything <1MB is fine for most desktop/server use; performance and compile time matter more.
- Others, especially in embedded/Linux‑on‑constrained‑devices and low‑resource containers, emphasize that kilobytes add up and can be a hard constraint.
- There’s also an environmental/efficiency angle: large binaries and wasted work are seen as systemic bloat.
Static vs dynamic linking and ABI stability
- Rust defaults to static std because its ABI is unstable; dynamic Rust libraries are possible but discouraged.
- C ABI can be used for stable shared objects callable from other languages.
- Embedded Rust often uses
#![no_std], which bypasses much of this overhead; but many “embedded” Linux systems still care about full‑fat std binary sizes.
Project process and attitudes
- Some criticize that this “obvious” issue lingered for ~7 years, seeing it as a sign of focus on features over fundamentals.
- Others frame it as normal prioritization: few users were blocked, there were workarounds, and many more urgent problems competed for attention.
- Broad agreement that improving defaults, even for issues with known workarounds, is important for Rust’s polish and perception.