Just having found Rust and having read the first two chapters of the documentation, I find the approach and the way they defined the language particularly interesting. So I decided to get my fingers wet and started out with Hello world...
I did so on Windows 7 x64, btw.
fn main() {
println!("Hello, world!");
}
Issuing cargo build
and looking at the result in targets\debug
I found the resulting .exe
being 3MB. After some searching (documentation of cargo command line flags is hard to find...) I found --release
option and created the release build. To my surprise, the .exe size has only become smaller by an insignificant amount: 2.99MB instead of 3MB.
So, confessing I am a newbie to Rust and its ecosystem, my expectation would have been that a Systems Programming language would produce something compact.
Can anyone elaborate on what Rust is compiling to, how it can be possible it produces such huge images from a 3 liner program? Is it compiling to a virtual machine? Is there a strip command I missed (debug info inside the release build?)? Anything else which might allow to understand what is going on?
- 8I think 3Mb contains not only Hello World, but also all the needed environment for the platform. The same thing can be seen with Qt. That does not mean if you write a 6-line program the size will become 6 Mb. It will stay at 3Mb and will grow very slowly after that. – Andrei Nikolaenko Mar 12 '15 at 11:11
- 12@AndreiNikolaenko I am aware of that. But this hints that either they do not handle libraries as C does, adding only what is required to an image or that something else is going on. – BitTickler Mar 12 '15 at 11:18
- 1@user2225104 See my answer, RUST handles libraries in the same (or similar) way as C does, but by default C does not compile static libraries into your program (at least, on C++). – AStopher Mar 12 '15 at 11:20
- 2Very related: Do DLLs built with Rust require libgcc.dll on run time?. – rubenvb Mar 12 '15 at 13:29
- 5Is this outdated now? With rustc version 1.35.0 and no cli options I get an exe that is 137kb in size. Does it automatically compile dynamically linked now or did something else happen in the meantime? – itmuckel Jun 28 '19 at 15:47
Rust uses static linking to compile its programs, meaning that all libraries required by even the simplest Hello world!
program will be compiled into your executable. This also includes the Rust runtime.
To force Rust to dynamically link programs, use the command-line arguments -C prefer-dynamic
; this will result in a much smaller file size but will also require the Rust libraries (including its runtime) to be available to your program at runtime. This essentially means you will need to provide them if the computer does not have them, taking up more space than your original statically linked program takes up.
For portability I'd recommend you statically link the Rust libraries and runtime in the way you have been doing if you were to ever distribute your programs to others.
- 4@user2225104 Unsure about Cargo, but according to this bug report on GitHub, this isn't yet possible unfortunately. – AStopher Mar 12 '15 at 11:27
- 25I don't think static linking explains the huge HELLO-WORLD. Shouldn't it only link in the parts of the libraries that are actually used, and HELLO-WORLD uses virtually nothing? – bobcat Aug 29 '16 at 5:21
- 11BitTickler
cargo rustc [--debug or --release] -- -C prefer-dynamic
– Zoey Mertes Sep 14 '16 at 17:55 - 6@Nulik: Yes, by default, but that's because Rust defaults to static builds (all dependencies, including runtime, included), while Go links its runtime dynamically. On my CentOS 7 system, Go's
helloworld
compiles to ~76K, but on top of standard stuff, it takes a runtime dynamic dependency onlibgo.so
, which is over 47M. The default Rusthelloworld
(as made withcargo new
) doesn't have any unique dynamic dependencies, holding everything but basic C runtime stuff in a 1.6M executable; with tweaks (optimize for size, using LTO, aborting on panic), it drops to 0.6M. – ShadowRanger Feb 21 '20 at 18:52 - 5The
-C prefer-dynamic
option gets the release builds (with only optimize for size enabled; it wouldn't let me use LTO or abort on panic) down to 8.8K, albeit with a new 4.7M dynamic dependency. So apples-to-apples, Rust is smaller; it's a tenth the size dynamically linked, relying on a runtime that's a tenth the size as well. – ShadowRanger Feb 21 '20 at 18:59
I don't have any Windows systems to try on, but on Linux, a statically compiled Rust hello world is actually smaller than the equivalent C. If you are seeing a huge difference in size, it is probably because you are linking the Rust executable statically and the C one dynamically.
With dynamic linking, you need to take the size of all the dynamic libraries into account too, not just the executable.
So, if you want to compare apples to apples, you need to make sure either both are dynamic or both are static. Different compilers will have different defaults, so you can't just rely on the compiler defaults to produce the same result.
If you're interested, here are my results:
-rw-r--r-- 1 aij aij 63 Apr 5 14:26 printf.c -rwxr-xr-x 1 aij aij 6696 Apr 5 14:27 printf.dyn -rwxr-xr-x 1 aij aij 829344 Apr 5 14:27 printf.static -rw-r--r-- 1 aij aij 59 Apr 5 14:26 puts.c -rwxr-xr-x 1 aij aij 6696 Apr 5 14:27 puts.dyn -rwxr-xr-x 1 aij aij 829344 Apr 5 14:27 puts.static -rwxr-xr-x 1 aij aij 8712 Apr 5 14:28 rust.dyn -rw-r--r-- 1 aij aij 46 Apr 5 14:09 rust.rs -rwxr-xr-x 1 aij aij 661496 Apr 5 14:28 rust.static
These were compiled with gcc (Debian 4.9.2-10) 4.9.2 and rustc 1.0.0-nightly (d17d6e7f1 2015-04-02) (built 2015-04-03), both with default options and with -static
for gcc and -C prefer-dynamic
for rustc.
I had two versions of the C hello world because I thought using puts()
might link in fewer compilation units.
If you want to try reproducing it on Windows, here are the sources I used:
printf.c:
#include <stdio.h>
int main() {
printf("Hello, world!\n");
}
puts.c:
#include <stdio.h>
int main() {
puts("Hello, world!");
}
rust.rs
fn main() {
println!("Hello, world!");
}
Also, keep in mind that different amounts of debugging information, or different optimization levels would also make a difference. But I expect if you are seeing a huge difference it is due to static vs. dynamic linking.
- 38gcc is smart enough to do exactly the printf -> puts substitution itself, that's why results are identical. – bluss May 29 '15 at 11:29
- 13As of 2018 if you want a fair comparison do remember to "strip" the executables, as a hello world Rust executable on my system is a whopping 5.3MB but drops down to less than 10% of that when you remove all the debug symbols and such. – Matti Virkkunen Jul 1 '18 at 7:27
- @MattiVirkkunen: Still the case in 2020; the natural size seems smaller (nowhere near 5.3M), but the ratio of symbols to code is still pretty extreme. The debug build, purely default options on Rust 1.34.0 on CentOS 7, stripped with
strip -s
, drops from 1.6M to 190K. The release build (defaults plusopt-level='s'
,lto = true
, andpanic = 'abort'
to minimize size) drops from 623K to 158K. – ShadowRanger Feb 21 '20 at 19:07
For an overview of all of the ways to reduce the size of a Rust binary, see the min-sized-rust
repository.
The current high level steps to reduce binary size are:
- Use Rust 1.32.0 or newer (which doesn't include
jemalloc
by default) - Add the following to
Cargo.toml
[profile.release]
opt-level = 'z' # Optimize for size.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = 'abort' # Abort on panic
- Build in release mode using
cargo build --release
- Run
strip
on the resulting binary.
There is more that can be done using nightly
Rust, but I'll leave that information in min-sized-rust
as it changes over time due to the use of unstable features.
You can also use #![no_std]
to remove Rust's libstd
. See min-sized-rust
for details.
When compiling with Cargo, you can use dynamic linking:
cargo rustc --release -- -C prefer-dynamic
This will dramatically reduce the size of the binary, as it is now dynamically linked.
On Linux, at least, you can also strip the binary of symbols using the strip
command:
strip target/release/<binary>
This will approximately halve the size of most binaries.
- 13Just some stats, default release version of hello world (linux x86_64). 3.5 M, with prefer-dynamic 8904 B, stripped 6392 B. – Zitrax May 24 '17 at 22:51
if you are using "rustc" command to compile just write command "rustc -C prefer-dynamic=yes main.rs"
- 8This is already mentioned in the accepted answer; this answer provides no new value. – Shepmaster Nov 10 '20 at 14:00
This is a feature, not a bug!
You can specify the library versions (in the project's associated Cargo.toml file) used in the program (even the implicit ones) to ensure library version compatibility. This, on the other hand, requires that the specific library be statically linked to the executable, generating large run-time images.
Hey, it's not 1978 any more - many people have more than 2 MB RAM in their computers :-)
- 12specify the library versions [...] requires that the specific library be statically linked — no, it doesn't. Plenty of code exists where exact versions of libraries are dynamically linked. – Shepmaster Feb 15 '18 at 14:40