Rust vs GoLang deployment: statically linked binaries

Current understanding:

  1. GoLang by default produces statically linked binaries.
  2. RustLang by default produces dynamically linked binaries.
  3. For deployment, statically linked binaries are easier to use.

Assuming the above is correct, which I am not 100% sure on: (1) is there a reason Rust goes for dynamic, Go goes for static, (2) is there an easy way to make Rust go for static ?

Are you looking to statically link external or system dependencies (like the CRT)? Or both?

As far as I have gathered, Rust uses static linking for crates -- so if you use pure Rust crates you'll get static linking, albeit it dynamically links against system libraries like the C runtime, which presumably will "always" exist.

Crates which pull in external libraries can choose how they link. rusqlite, for instance, can link against an existing library, but also supports building - and statically linking - against a supplied version of sqlite.h/.c.

I don't recall seeing a way to tell cargo to "dynamically link everything" or "statically link everything" -- I think it's something crate developers choose to support on a crate-by-crate basis.

With all that said, perhaps it's possible to pass something like -Bstatic using some global linker flag, and then check to make sure that ldd doesn't output any dynamic libraries.

On Windows you can choose to statically link the CRT by putting:

[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]

.. in .cargo/config.toml.

I will admit up front I don't know much about this. I am running Linux. My XY problem is that I have written a server in Rust, now need to deploy it on to VPS nodes, and want to do this as easily as possible. An entirely statically linked binary seems optimal in this situation.

I haven't played with this, I just searched around a bit out of curiosity. What I found was:

My unconfirmed, surface-level impression from those links is "possible but potentially painful". But again, I have no experience trying it myself.

I've had success with the muslrust Docker image, but you have to keep in mind that static linking with libc and other libraries (OpenSSL and so on) might raise some link time problems that are hard to diagnose.

Rust and Go have almost identical approaches towards linking, and both pure Rust and pure Go programs will statically link everything except maybe libc [1].

Rust makes using native dependencies a lot easier, so you are more likely to run into a dependency which requires dynamic linking. However, the culture among *-sys crate authors is to heavily prefer static linking because that means you can copy binaries around and have everything still work (i.e. your exact use-case).

99% of the time, if your VPS node is running Linux, you should be able to compile your binary in CI and copy it across and everything will just work.

If the VPS node may also be Windows or MacOS then I would make CI compile the binaries on a Windows/MacOS machine so you don't need to worry about cross-compilation[^2].

Another solution is to wrap your binary up in a Docker container so it always has any native dependencies it needs. For most cloud applications this is the preferred option because it works well with things like docker-compose, Kubernetes, Amazon Elastic Container Service, and so on.


  1. As of version 1.11, Go needs to dynamically link to libc on MacOS if it wants portable binaries. I'm not sure what they do on Windows, but the Windows CRT isn't stable so they probably dynamically link to that too. ↩ī¸Ž

3 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.