Rust creates huge files

A little Hello-World program in Java creates a *.class file, which is 417 bytes small.

A Hello-World program in C# creates a *.exe file , which is 3,584 bytes big.

Ok, both are for Virtual Machines.

But a Hello-World program in C created with MinGW64 for Windows, creates a *.exe file, which is 48,432 bytes big.

And I have downloaded rust-1.64.0-x86_64-pc-windows-gnu.msi and compiled with it a Hello-World program.

That *.exe file, which is 4,721,771 bytes big!

It is ca. 100 times bigger then the output of the C program.

I know, how bigger the own program will be, the lesser is the size-difference.

But why is it so big?

Ok, you have also a version created with MSVC and which using the MS C++ runtime.
But I think a comparison of a program compiled with an GNU C compiler and an Rust compiler based on and using GNU tools, is a good comparison.

The same old question again. Rust links system libraries statically by default. Static linking is superior for many reasons (compatibility, reliability, security), but you can turn it off if you really want to.

It's not like all Rust programs end up being 100 times bigger than equivalent C programs. If you link the C program statically, the difference basically disappears.

Also, why do you think that a Hello world program is a good example for benchmarking compiler tooling/language infrastructure in general? It's not nearly a realistic example of anything you'll ever write, so you shouldn't draw far-reaching conclusions from it.

2 Likes

You need to use cargo build --release to make reasonably-sized executable files. By default it builds in debug mode, which adds a few megabytes of debug information pulled in from Rust's standard library. You may also need to set [profile.release] strip = true in Cargo.toml to make sure the debug info is gone.

Note that most Hello World executables from C are incapable of printing anything. They dynamically link to libc that actually does the work, and on my OS (macOS) that is 30MB large. If you link in libc statically, it's usually closer to Rust's hello world size.

Rust's hello world also pulls in a bunch of its standard library, because handles I/O errors, and that means it includes error reporting and exception handling machinery. This is a one-time cost, so it doesn't grow when your program grows.

If you don't use Rust's standard library, you can trim it down to 3KB.

8 Likes

"If you link the C program statically"

I dont know, if the C-program is compiled statically.
But I can move it on any place on Windows and it runs.
And I have just now tested it on Linux with WINE. There I haven't installed mingw, but it works on WINE without additional libraries,, too.

Thx to you two.
I will now read the blog post.
But the standard library will be automattical be linked in, right?
I have now compiled a minimal program, without println

fn main() {
}

and without changing the mentioned files, it is still 4,720,938 bytes big.

The default cargo build and cargo run builds executables for development and debugging, not for distribution. It intentionally makes them super fat, with over 2MB of debug information, extra runtime checks, and unoptimized code. This is intentional dont-care-about-exe-sizes mode that prioritizes compilation speed and debugging experience over everything else.

The standard library and all that jazz will be linked in debug mode even if your main is empty. Removal of unused code is not done in the debug mode. It's a "throw everything in quickly, no time to check if it's useful or not" mode.

Cargo's mode for caring about executable sizes is cargo build --release and it should give you executable about 400KB large.

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

With a few setting tweaks, like dead code removal (LTO) and stripping, it should be down to 250KB or so.

And with this:

[dependencies]
libc = { version = "0.2", default-features = false }
#![no_std]
#![no_main]

#[no_mangle]
pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize {
    const HELLO: &'static str = "Hello, world!\n\0";
    unsafe {
        libc::printf(HELLO.as_ptr() as *const _);
    }
    0
}

#[panic_handler]
fn my_panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

It's 50KB (tested on macOS). It can be trimmed down to 33KB by completely removing ability to display panics.

6 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.