There was a time where this debate was bigger. It seems the world has shifted towards architectures and tooling that does not allow dynamic linking or makes it harder. This compromise makes it easier for the maintainers of the tools / languages, but does take away choice from the user / developer. But maybe that’s not important? What are your thoughts?
Some languages don’t even support linking at all. Interpreted languages often dispatch everything by name without any relocations, which is obviously horrible. And some compiled languages only support translating the whole program (or at least, whole binary - looking at you, Rust!) at once. Do note that “static linking” has shades of meaning: it applies to “link multiple objects into a binary”, but often that it excluded from the discussion in favor of just “use a .a instead of a .so”.
Dynamic linking supports much faster development cycle than static linking (which is faster than whole-binary-at-once), at the cost of slightly slower runtime (but the location of that slowness can be controlled, if you actually care, and can easily be kept out of hot paths). It is of particularly high value for security updates, but we all known most developers don’t care about security so I’m talking about annoyance instead. Some realistic numbers here: dynamic linking might be “rebuild in 0.3 seconds” vs static linking “rebuild in 3 seconds” vs no linking “rebuild in 30 seconds”.
Dynamic linking is generally more reliable against long-term system changes. For example, it is impossible to run old statically-linked versions of bash 3.2 anymore on a modern distro (something about an incompatible locale format?), whereas the dynamically linked versions work just fine (assuming the libraries are installed, which is a reasonable assumption). Keep in mind that “just run everything in a container” isn’t a solution because somebody has to maintain the distro inside the container.
Unfortunately, a lot of programmers lack basic competence and therefore have trouble setting up dynamic linking. If you really need frobbing, there’s nothing wrong with RPATH if you’re not setuid or similar (and even if you are, absolute root-owned paths are safe - a reasonable restriction since setuid will require more than just extracting a tarball anyway).
Even if you do use static linking, you should NEVER statically link to libc, and probably not to libstdc++ either. There are just too many things that can go wrong when you given up on the notion of “single source of truth”. If you actually read the man pages for the tools you’re using this is very easy to do, but a lack of such basic abilities is common among proponents of static linking.
Again, keep in mind that “just run everything in a container” isn’t a solution because somebody has to maintain the distro inside the container.
The big question these days should not be “static or dynamic linking” but “dynamic linking with or without semantic interposition?” Apple’s broken “two level namespaces” is closely related but also prevents symbol migration, and is really aimed at people who forgot to use
-fvisibility=hidden
.This is definitely not sound. You should never statically link against glibc as glibc does some very unsound things under the hood like load NSS modules. Static linking against a non-bloatware libc is fine in most cases, as kernel interfaces break rarely, or rather, because Kernel devs go to extreme lenghts not to break user space, and they do a fantastic job too.
The problem is that GLIBC is the only serious attempt at a libc on Linux. The only competitor that is even trying is MUSL, and until early $CURRENTYEAR it still had worldbreaking standard-violating bugs marked WONTFIX. While I can no longer name similar catastrophes, that history gives me little confidence.
There are some lovely technical things in MUSL, but a GLIBC alternative it really is not.
I would not agree with the “only serious attempt” path. The problem that most other libc are not drop in replacements has little to do with standard compliance and a lot to do with the fact that software is so glued to glibc behavior that you would have to be bug for bug compatible to achieve that goal, which imo is not only unrealistic, it’s also very undesirable.
Can you share a link? I’d be genuinely interested.
DNS-over-TCP (which is required by the standard for all replies over 512 bytes) was unsupported prior to MUSL 1.2.4, released in May 2023. Work had begun in 2022 so I guess it wasn’t EWONTFIX at that point.
Here’s a link showing the MUSL author leaning toward still rejecting the standard-mandated feature as recently as 2020: https://www.openwall.com/lists/musl/2020/04/17/7 (“not to do fallback”)
Complaints that the differences are just about “bug-for-bug compatibility” are highly misguided when it’s useful features, let alone standard-mandated ones (e.g. the whole complex library is still missing!)
Ah, yes, okay, that drama.
This is really only true for
glibc
(because its design doesn’t play nice with static linking) and whatever macOS/Windows have (no stable kernel interface, which Go famously found out the hard way.)Granted, most of the time those are what you’re using, but there’s plenty of cases where statically linking to MUSL
libc
makes your life a lot easier (Apline containers, distributing cross-distro binaries.)