A Vision for Cargo
38 points by asb
38 points by asb
To me lack of good CI caching support is the biggest drawback of Cargo.
The target dir is enormous and opaque to users. Hosted CI providers don't integrate with it (other than by giving an option to download and re-upload the whole thing as a tarball, no incremental update).
sccache doesn't work as well as it could, because it has to be very conservative due to lack of true dependency information from Rust/Cargo (it tries to reconstruct it from rustc args and cargo env vars, but that's not quite correct in edge cases).
And it's soooo easy to invalidate the whole build cache due to a triviality. For ages Mac Docker had a bug that truncated nanoseconds from some filesystem timestamps, making crates with build.rs uncacheable 50% of the time.
The pain is so big, that at my $WORK big projects are switching from Cargo to Bazel. I don't find Bazel pleasant to work with, especially the weirdness of reimplementing entire cargo as a Bazel plugin, but it can do caching.
We use Bazel at work to replace Cargo and I honestly think it's fine. Cargo doesn't necessarily need to be optimized for super-monorepos, when it does work for everyone pushing things to crates.io, because Bazel is actually really nice when you're a large enough enterprise to have a dedicated Bazel guy managing it, and it simplifies things you're a lot more likely to do like vendoring and patching dependencies. Nor will 'better' caching fix it - artifact dependencies are a blocker for Bazel cases like ours given Cargo's hard requirement of one true target platform per top level command.
It's not an issue only for super monorepos though? A moderately sized project with a moderately deep dependency graph still wants to cache CI builds (especially with how slow many CI runners are), and they definitely want to cache downloads from crates.io just to avoid unnecessarily taxing those servers. Yet target folders for moderately sized repos grow gigantic over time if you don't clean them.
The deprecations have been put behind a deprecated feature flag because many people turn warnings into errors, including deprecations, and having them on by default coupled upgrading with resolving deprecations increasing costs and risks.
In ratatui, we've never had anyone complain about features that we #[deprecate] in one version and then drop at some later version. So I don't personally see the need to cater for this approach, it's a technically correct solution that has one too many moving pieces. We do also invest effort into our Breaking changes doc.
We do have the opposite bit though - the #[unstable] flag via https://crates.io/crates/instability (which we forked from https://crates.io/crates/stability because we needed more options more regularly). Something like this should be built in to rust std lib IMO. (there's an RFC probably)
A major enabler for thinking is to have all the context in one place by gathering all of the relevant information from the thread and summarizing it for easy consumption
I feel like this is the place where cheap fast good LLM tooling comes into the fore. We're not there yet, but optimizing workflows for attention is something that the industry really needs to get their head around.
According to Stack Overflow, cargo is the most desired development tool. Many people attribute their choice of Rust to Cargo.
I find this so baffling. I love Rust, and try to use it whenever I have a chance. And Cargo is fine, don't get me wrong. But it boggles my mind that it's somehow Rust's best attribute for some people. I'd really like to understand!
If Cargo went away tomorrow, nothing would change in my view of Rust. Likewise if it were massively "improved", whatever that might mean.
Once you're in the rust ecosystem, cargo tends to just disappear from view, you build up the knowledge of what bits are meaningful and how they fit together. But coming from outside of it, there's lots of friction points that having a single known good tool that is the blessed way to develop is something that various languages often don't have the same perspective on.
In java, do you use gradle, maven, ant? In python are you using uv, ... Ruby - rvm, rubenv, ? In C/C++ Do you maintain makefiles or cmake? What's your mechanism for getting the right deps from external sources?
For a lot of those little problems, cargo just works.
I'm in no way against Cargo, but I also don't really agree with you.
In java, do you use gradle, maven, ant? In python are you using uv, ... Ruby - rvm, rubenv, ? In C/C++ Do you maintain makefiles or cmake?
Yes :-)
What's your mechanism for getting the right deps from external sources?
That's in my honest opinion outside of the scope of the build system. I guess it's why I'm one of those weirdos who places Cargo in always-offline mode and replaces its cargo.io source with Debian's directory of apt-managed Rust packages.
I know I'm in a tiny minority. I like Cargo as a build system just fine. But I have no interest in it going off downloading and "managing" dependencies for me.
Ok, so you don't need cargo because it doesn't match your workflows. That makes sense. Do you think those workflows are generally accessible, or more niche to your perspective?
How much experience have you had catering to C/C++, possibly java or JS, build systems and pipelines? And keeping them running over years?
If you had, especially the C/C++, you would understand. You can run test and it just works in one command. You can compile in one command and it just works. You can cross compile and it just works. You can change produce a debug build or a prod build in a command without touching anything and it Just Works!!!!! Also you can run the same command on Mac and on Windows and on Linux and It Also Just Works!!!!!!!!
WTF Is this black magic!!!!1
None of that is normal in these environment. Getting anything with a dependency to compile is usually a multi hour journey, and not a nice one. One in which you lose sanity and bash your head against the desk multiple time, if you can even get it to work.
Cargo is the best thing that happened to system level people since... probably since the 80s.
How much experience have you had catering to C/C++, possibly java or JS, build systems and pipelines? And keeping them running over years?
Moderate for C and C++. None for Java and JS. I'm a Debian Developer and work in research – I've seen some shit out there :-)
If you had, especially the C/C++, you would understand. You can run test and it just works in one command. You can compile in one command and it just works. You can cross compile and it just works. You can change produce a debug build or a prod build in a command without touching anything and it Just Works!!!!! Also you can run the same command on Mac and on Windows and on Linux and It Also Just Works!!!!!!!!
I usually find that this works in well-maintained packages in other languages too. To me, the difference is just that Cargo makes it so easy that a lot fewer packages are complete shitshows in this department (compared especially to C++). That's good, I guess.
None of that is normal in these environment. Getting anything with a dependency to compile is usually a multi hour journey, and not a nice one. One in which you lose sanity and bash your head against the desk multiple time, if you can even get it to work.
Definitely the case sometimes. But it's usually because the developers have either not given any care to the build system, or (more likely) subscribe to the philosophy of "I'm gonna hardcode a bunch of things that make it Work In The Cases That People Most Often Complain About, best practices, adoptibility and generality bedamned". This is especially the case in academic code. And it's a big red flag.
I do appreciate that Cargo makes people have to go out of their way to do crap like that. It's a good thing.
If you're coming from C/C++, cargo is like a multi generational leap forward in build and dependency management (because it is). Even if you barely use any dependencies.
Yeah see, I just disagree on the build side. (Again: it's fine, nothing particularly wrong with it. But I also don't find it mindblowingly amazing.) And I turn it off on the dependency management side.
The important thing is that everyone in the Rust ecosystem agreed to use Cargo. The value of interoperability and elimination of snowflake projects is greater than Cargo's actual feature set.
When I say 'Rust is the greatest', it's hyperbole, but when I say 'Cargo is the greatest', it isn't. I have used a lot of different dependency systems and Cargo, or rather the Cargo experience, which includes Rust features like cfg and other tools like rustdoc, seems obviously superior to all of them. Assumption of semver, being able to depend on more than one version of a crate at a time, principled separation of dependency node info from local build profile info, supreme empowerment of build scripts, automatic locking and installation, and many other things besides, all come together to make cargo build not only Just Work but be almost friction-free ~100% of the time as both a consumer and a producer. I spend so much time in Rust world that it's always a culture shock when I return to some other environment temporarily. You forget that, in Gradle, all your dependencies must be using the same version of a library, and that working around this requires both a plugin and a new task graph endpoint.
Agree on cfg, rustdoc and semver (and that Gradle looks like a nightmare – I've only seen it as a spectator thankfully). But these aren't Cargo – cfg is a Rust feature, rustdoc is a standalone tool, and semver is "just" a culture (an excellent one).
supreme empowerment of build scripts,
Cargo's support for build scripts is fine. But every time I have to use it, I find myself feeling that I'm writing a fragile manual build system except in Rust instead of Make. I love Rust, but maybe not for that task.
When you say dep = "ver", you are implicitly saying dep = "^ver", i.e. that the version will be selected based on semver compatibility. It is a 'culture' of everyone using it, I guess, but it's deliberately created by having it be required by the defaults, and it is definitely Cargo implementing the semver compatibility algorithm.
Short of fully supporting open namespaces, it would be nice to have a way of decoupling the unit of dependency from the unit of compilation, and making this explicit in Cargo.lock/cargo tree. So for example if you have a workspace of 12 crates with a single root crate reexporting its 11 siblings it would be nice to treat the entire workspace as a dependency instead of each individual member of that workspace.
It's mostly cosmetic but it at least gives you a way to correctly enumerate the number of logical dependencies you have in a project.