Shrinking a Rust binary output from 18mb to 7mb

Simple tweaks to the options in the release profile can dramatically reduce the footprint of the resulting binary

After spotting an 18mb binary lurking in the node_modules folder of a project I'd released with napi-rs, I was curious about how much I could reduce it.

I was convinced of two things:

  • First, that I'd only be able to trim a megabyte here or there with a combo of various release options.
  • Second, that any larger wins would come from considering which 3rd-party crates I'm using and swapping them out for lighter weight ones.

In the end, I was so wrong on the first part, that I thought it was worth sharing in this post. If I end up tweaking 3rd party dependencies to further reduce binary size, I'll be sure to document that process too.

Journey

I was reading this post Trimming down a rust binary in half , but it seemed like swapping out dependencies was a much bigger task than I wanted to take on right now. The post eventually links out to min-sized-rust though—so I started copy/pasting options from there seeing where it led me 🤣.

Impact

Well, with zero changes to dependencies, I cut 11mb from the binary size! I've documented each option I added and what difference it made to the output size 🤌

In each case, I'm just adding another line within the [profile.release] section of Cargo.toml, and re-running this:

cargo build --release

Note: If you're using napi-rs, these same optimizations will apply when you run the relevant build command ✅

Cargo Options AppliedBinary SizeDifference
1None18mb-
2[profile.release]
strip = true
14mb-4mb
3[profile.release]
strip = true
opt-level = "z"
12mb-2mb
4[profile.release]
strip = true
opt-level = "z"
lto = true
8.1mb-3.9mb
5[profile.release]
strip = true
opt-level = "z"
lto = true
codegen-units = 1
7.9mb-0.2mb
6[profile.release]
strip = true
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
7.0mb-0.9mb

Result - 11mb smaller overall!

Now, when I publish my program to npm, users are only downloading a 7mb binary, compared to an 18mb one 🎉. Of course, I can further shrink this—I'm not exactly happy with it being 7mb, but I did find it interesting that with zero code/dependency changes I was still able to shrink the size so much!

Note: In my use-case, I'm optimizing for size over performance (with opt-level = "z") since I'm building a development server where raw-performance is not the primary concern

Copy/Paste

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

Legend: Explanation of Cargo Options (this part is from ChatGPT)

OptionExplanation
strip = trueRemoves all debug symbols from the binary, which reduces the size by eliminating unused metadata.
opt-level = "z"Optimizes for binary size instead of performance, producing a smaller binary at the cost of runtime speed.
lto = trueEnables Link Time Optimization, which performs cross-module optimization at the linking stage, reducing size and improving performance.
codegen-units = 1Limits the compiler to use a single code generation unit, allowing for better optimization across the entire crate at the expense of compilation time.
panic = "abort"Configures the binary to abort on panic instead of unwinding, saving space by not including unwinding code.