State of the art
I believe I surprise no-one when mentioning that Rust is slow to build.
Efforts in improving Rust compilation times abound:
- Parallel front-end,
- Incremental builds,
- Cranelift,
- ...
This post is about linking. The recent switch to lld
by default (on Linux) has helped, but still, linking a static binary is not fast. In fact, with incremental builds, it often dominates build times entirely.
There are alternative linkers:
mold
(orsold
) may help, depending on the platform.- David Lattimore aims to build an incremental linker.
Yet, it seems that one solution remains unexplored here: dynamic linking, or how to avoid painfully slow link times altogether.
Idea
The benefit of static binaries should not be underestimated. The ability to just copy/paste one single binary across hosts, and have it work, is gold. It's unclear how often such binaries are moved, but it is clear that there are cargo commands during which they are not moved: cargo test
and cargo run
.
A simple idea to bypass link time pains thus arises:
- Add a new flag,
--placeholder
, tocargo build
. When used, library dependencies are compiled as dynamic libraries, instead of static ones, and the (eventual) binary uses rpath, or appropriate platform equivalent, to reference their location so noLD_LIBRARY_PATH
trick is necessary. - Have
cargo test
andcargo run
default to dynamic linking.
Folks who invoke cargo build
manually, without specifying the flag, will still build statically linked binaries, and may move them like they are used. Folks who use cargo test
and cargo run
will benefit from instant link times.
Q&A
But Rust doesn't have a stable ABI!
A stable ABI is only necessary when building against a version V with compiler C and set of flags F then attempting to link with a version V', built with a compiler C', and set of flags F'.
This usecase would remain unsupported (no guarantees), but is of no consequence to this proposal. In our case, cargo build --placeholder
will build an up-to-date version of each dynamically linked dependency, with the same compiler & flags as the binary.
The very architecture which guarantees incremental builds work (same compiler, flags, deps, etc...) will underpin the stability of this arrangement.
Can I call the binary myself?
Yes, of course. Indeed, by using rpath, the loader will know where to look for the dynamic libraries (where they were build) of a binary without any further hint.
The only restriction is that such a binary is no longer self-contained, so it cannot easily be moved to another host -- though with absolute rpaths it could be moved around on the same host -- and that the dynamic libraries may be removed, leaving the loader dangling.
How are generic and inline methods handled?
Just like today. This means they may requiring codegen work in the final binary, even when dynamic linking the dependency. Thus a dependency with a heavily generic interface (and a slim or no non-generic core) would not benefit much. All other dependencies will, however, so hopefully the speed-up will still be notable on most projects.
And what if ...?
Please ask questions. I don't promise to have all the answers -- I'm not a specialist of the question -- but if we come all together, we'll figure them out!