5 ms·
> Most shells take less space than that! Most shells dynamically link to a runtime your OS provides "for free". The 4.3 MiB binary in question is bundling the
by koito17 1y ago
> Most shells take less space than that!
Most shells dynamically link to a runtime your OS provides "for free". The 4.3 MiB binary in question is bundling the Rust runtime and its dependencies.
For reference, a statically-compiled C++ "Hello, World" is 2.2 MiB after stripping.
% cat hello.nix
{
pkgs ? import <nixpkgs> { crossSystem = "aarch64-linux"; }
}:
pkgs.stdenv.mkDerivation {
name = "hello-static";
src = pkgs.writeText "hello.cpp" ''
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
'';
dontUnpack = true;
buildInputs = [ pkgs.glibc.static ];
buildPhase = "$CXX -std=c++17 -static -o hello $src";
installPhase = "mkdir -p $out/bin; cp hello $out/bin/";
}
% nix-build hello.nix
...
% wc -c result/bin/hello
2224640 result/bin/hello
- esafak 1y ago2.2MiB for "Hello, World"? I must be getting old... The executable takes 33KB in C, 75KB in nim.
- creatonez 1y ago> The executable takes 33KB in C, 75KB in nim. Did you statically link Glibc...? Or is this with a non-GNU libc? Either way, it probably is true that this 2.2MiB number would be smaller on, say, Debian 5. And much smaller on PDP Unix.
- koito17 1y agoBy switching to e.g. musl, you can go down to a single megabyte ;) But in all seriousness, my example is quite cherrypicked, since nobody will actually statically link glibc. And even if they did, one can make use of link-time optimization to remove lots of patches of unused code. Note that this is the same strategy one would employ to debloat their Rust binaries. (Use LTO, don't aggressively inline code, etc.)
- 3836293648 1y agoJust a `puts("Hello world!")` with -Os statically linked to musl is 22k
- 3836293648 1y agoWe just have large standard libraries now
- surajrmal 1y agolto will remove most of it.
- AdieuToLogic 1y agoJust for fun, I wondered how small a canonical hello world program could be in macOS running an ARM processor. Below is based on what I found here[0] with minor command-line switch alterations to account for a newer OS version. ARM64 assembly program (hw.s): // // Assembler program to print "Hello World!" // to stdout. // // X0-X2 - parameters to linux function services // X16 - linux function number // .global _start // Provide program starting address to linker .align 2 // Setup the parameters to print hello world // and then call Linux to do it. _start: mov X0, #1 // 1 = StdOut adr X1, helloworld // string to print mov X2, #13 // length of our string mov X16, #4 // MacOS write system call svc 0 // Call linux to output the string // Setup the parameters to exit the program // and then call Linux to do it. mov X0, #0 // Use 0 return code mov X16, #1 // Service command code 1 terminates this program svc 0 // Call MacOS to terminate the program helloworld: .ascii "Hello World!\n" Assembling and linking commands: as -o hw.o hw.s && ld -macos_version_min 14.0.0 -o hw hw.o -lSystem -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.2.sdk -e _start -arch arm64 Resulting file sizes: -rwxr-xr-x 1 <uid> <gid> 16K Jun 18 21:23 hw -rw-r--r-- 1 <uid> <gid> 440B Jun 18 21:23 hw.o -rw-r--r-- 1 <uid> <gid> 862B Jun 18 21:21 hw.s 0 - https://smist08.wordpress.com/2021/01/08/apple-m1-assembly-language-hello-world/ https://smist08.wordpress.com/2021/01/08/apple-m1-assembly-l...
- wahern 1y ago> Most shells dynamically link to a runtime your OS provides "for free" Rust binaries also dynamically link to and rely on this runtime.
- mtndew4brkfst 1y agoThat's not intrinsically or pervasively true, although it's not uncommon.
- creatonez 1y agoYes. The main areas where Rust code uses dynamic linking by default are Glibc and OpenSSL (through the popular `native-tls` crate). Most things outside of that will be statically linked by default. There is room to improve the situation by making more C wrapper libraries support either method. For Rust code talking to Rust libraries (such as `std`), it's a totally different challenge, similar to what you face in C++. You can compile your pure Rust app in a way to divide it up into DLLs. The problem is that the polymorphism in Rust means these DLLs must all be built together. Calling a polymorphic function means code has to be generated for it. The only way around this is for your Rust library to speak the C ABI, which bloats code as building your C-style API surface will resolve all the polymorphized functions/structs, but at least gets you swappable dynamic linking.
- wahern 1y agoUnless the majority of Rust builds on Linux statically link musl libc or use no_std, then it's pervasively true. And it's true on most non-Linux targets, including the BSDs and macOS. It's the same situation with Go.
- 3836293648 1y agoThe only way to avoid it is to be on linux with no_std, use musl statically or be on embedded. You cannot (or at least are supposed to not be able to) link to glibc statically and on every other OS you can only call syscalls via the system libraries. (Well, technically you can on most systems, it's just not stable across updates. OpenBSD will actively block it though)
- fuzztester 1y agowhy did you embed the c++ code in the .nix file? just to have everything in one file? how to show how to do it with nix? because it seem simpler to have a separate C++ file, and a simple shell script or makefile to compile it. e.g. although I could figure out roughly what the .nix file does, many more people would know plain unix shell than nix. and where is $out defined in the .nix file?
- AnthOlei 1y agoThe nix file is besides the point - it gives you a totally hermetic build environment. Not OP, but it’s the only way I know how to get gcc to use a static glibc. All you should pay attention to is that it’s using a static glibc. $out is a magic variable in nix that means the output of the derivation - the directory that nix moves to its final destination
- fuzztester 1y agothanks.
- JoshTriplett 1y ago> Not OP, but it’s the only way I know how to get gcc to use a static glibc. /tmp$ gcc -O3 test.c -o test /tmp$ ldd test linux-vdso.so.1 (0x00007f3d9fbfe000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3d9f9e8000) /lib64/ld-linux-x86-64.so.2 (0x00007f3d9fc00000) /tmp$ gcc -static -O3 test.c -o test /tmp$ ldd test not a dynamic executable
- 3836293648 1y agoI thought glibc had some hacks in it to prevent it from working fully when statically linked? Is this just a myth or outdated or only affects C/C++ or what?
- JoshTriplett 1y agoThe issue is that some features of glibc want to dlopen additional libraries, most notably NSS. If you call `gethostbyname`, even a static glibc will try to dlopen NSS libraries based on /etc/nsswitch.conf, and if the dynamic NSS libraries are incompatible with your statically linked glibc, you'll have problems. musl, by contrast, doesn't support NSS at all, only /etc/hosts and DNS servers listed in /etc/resolv.conf, so whether you statically or dynamically link musl, you just won't have any support for (for instance) mDNS, or dynamic users, or local containers, or various other bits of name resolution users may expect to Just Work.