Hacker News new | ask | show | jobs
by nicoburns 1516 days ago
The Rust ecosystem does one better and packages the C libraries and build configuration (including making it portable across platforms) as part of the crate. So you just add the dependency to your Cargo.toml and the C library will build as part of the regular `cargo build` process.
3 comments

Unless something has changed in the relatively recent past, I think you're overselling a fair bit. Not only does the package author have to understand the C dependency well enough to package it correctly on all platforms (basically by verifying the build in a hermetically sealed environment, and who is doing that?), but also the process for cross compiling is (or at least was) pretty complicated: https://www.modio.se/cross-compiling-rust-binaries-to-armv7..... And even then, I'm not sure this will yield a truly static binary (i.e., no dependency on libc).

In Go, it's just `CGO_ENABLED=0 GOARCH=armv7 GOOS=linux go build` for pure Go programs.

> (basically by verifying the build in a hermetically sealed environment, and who is doing that?)

Lots of people run stuff in CI, which isn't exactly that, but is close enough to make it not as big of a pain as it might otherwise be.

It can also help if their docs aren't great; I've looked at CI configs to realize how to install some sort of system dependency before.

> but also the process for cross compiling is (or at least was) pretty complicated

Most of this article is talking about installing and setting up both Docker and a C cross-compiled toolchain. So, you're right, but also not, sorta kinda. That is, this is certainly more hard than Go, but we're not talking about pure Rust at this point, so the fair comparison would be cgo with some C dependencies, which would also involve setting up a C cross-build toolchain, (and maybe docker). But at the same time, it doesn't have to be this way: Zig includes a full C cross toolchain in its compiler, so that you don't have to do this installation. It is, in my opinion, currently best-in-class here, far surpassing both Go and Rust.

It is also worth nothing that, IIRC, Go had to switch to dynamically linking libc on many platforms, since the idea of a "fully statically linked binary" is basically only coherent on Linux.

> Lots of people run stuff in CI, which isn't exactly that, but is close enough to make it not as big of a pain as it might otherwise be.

CI has a whole lot of variation. On the extreme end, there are people running Jenkins jobs on the same hosts as other jobs, and everyone just pre-installs whatever they need onto the base image for the host (i.e., not even working with a fresh OS image). Moreover, many people are just going to run their CI on amd64 Debian or RHEL and assume it works for all targets.

> That is, this is certainly more hard than Go, but we're not talking about pure Rust at this point, so the fair comparison would be cgo with some C dependencies

My whole thesis here is that Go leans less on FFI than other ecosystems, so you shouldn't need CGo in most cases where you would have to use FFI in other languages. It's a lot easier to get a pure-Go dependency tree than it is a pure-Rust dependency tree. Of course, that's an emergent property derived from weaknesses of Go's FFI, but it ends up being a really nice property in practice.

> But at the same time, it doesn't have to be this way: Zig includes a full C cross toolchain in its compiler, so that you don't have to do this installation. It is, in my opinion, currently best-in-class here, far surpassing both Go and Rust.

I think this is true if you assume that all ecosystems lean on C equally, but it's better by far to depend on C less because including the C cross toolchain doesn't absolve you from humans packaging C dependencies (in which case it's either easy because you neglect a bunch of packages or you test in a hermetic environment a la Nix and it becomes more bothersome than maintaining a pure-$hostLang version of the same package).

> Moreover, many people are just going to run their CI on amd64 Debian or RHEL and assume it works for all targets.

Rust has a strong concept of "tiered platforms", and so lots of people support at least Mac/Windows/Linux. Nobody uses Jenkins (for open source packages that will become your dependencies, at least), they use GitHub Actions or CircleCI, which make it easy to support many platforms. I personally run Windows, no WSL, and 99.99% of the time, everything Just Works for me.

But yes, that's why I wasn't saying it's purely just as good. For sure. But it does generally work well.

> My whole thesis here is that Go leans less on FFI than other ecosystems,

Gotcha, that's fair. Pure-x for any x often is really, really nice! Full agreement there.

> Gotcha, that's fair. Pure-x for any x often is really, really nice! Full agreement there.

Yeah, and it's really interesting how the relative ease/difficulty of FFI shapes an ecosystem. On one extreme, you have Go which has a lot of purity, but on the other you have Python where FFI is so easy that the maintainers can't really change anything including optimizations without breaking compatibility which means pure Python packages are relatively slow driving more reliance on FFI. It also means the package management tooling has to solve for the universe of C packages to be worthwhile, which drives a whole bunch of other problems. A decade ago, if you had asked me whether easy FFI was a good thing or a bad thing, I would have unequivocally said "a good thing". That might've been the correct answer if the lingua franca had a standard concept of reproducible builds.

While this is true in many cases, it’s worth pointing out that that is the choice of the package author, and is not always super simple to implement. So yeah, much of the time it is nice, but you’ll sometimes also run into these classic sorts of issues, either because the authors do not put in that work or because there’s a bug in the implementation.
And as a bonus, be statically linked with all the benefits that brings.
I recall statically compiling Rust to be a big pain (like, actually statically compiling, no dynamic dependencies on libc in Linux). I assume this is all the more true with arbitrary C dependencies?
If you have no C dependencies, it’s simple: you ask for the musl libc, and you’re done. It is not more onerous than go.

If you have C dependencies, then it does become a pain, depending on how well those dependencies' -sys packages interface with whatever build system they use.

I'm not talking about an actual static binary, I'm talking about a typical "mostly static" binary with the handful of common dynamic dependencies.