There's a recurring meme here and in other social media about rust being slow to compile, but anyone who's worked with Rust know that target dirs are a much more annoying papercut in practice than the relative speed of the compiler (which isn't appreciably worse than any other toolchain out there for the most common workflows).
A papercut is an unbelievable way to describe this. I tried forking Codex CLI and, for a terminal UI that manages chats and HTTP requests, it takes over 15 minutes to rebuild trivial changes on a top-shelf CPU with all incremental/caching optimizations, and tens of GBs in build caches. Neither of those is acceptable. Full stop. I am aware part of the problem is that Codex CLI is a vibe-coded monstrosity (surprise, software is not solved), but I've not had this problem with vibe-coded monstrosities in eg. Go. Hold your language to better standards. That doesn't mean abandon Rust; that means strive to make the Rust toolchain better than the Go toolchain rather than shrugging your shoulders and saying "lol this is fine no way anybody could do anything about this".
And neither the original example is "Rust compiles extremely slowly" (it does compile slowly), it's another example of "vibecoded software is of poor quality".
This is a tough sell. Yes, those languages (with some small nits about Ada) can accomplish the same safety guarantees, but they're nowhere performant enough in practice or memory efficient compared to Rust. A big part of the original mission of Rust was safety without performance compromises which for the most part its accomplished, though the price is paid during compilation. Further, their runtimes and limitations make it difficult to work directly with operating systems or low level system hardware. And I say this as someone who has written Haskell code in production.
That said, I would love for Idris to continue to develop. I'm a big believer in dependently typed languages and would actually improve correctness, assuming there are reasonable ways to avoid the other trappings of the language.
"but they're nowhere performant enough in practice or memory efficient compared to Rust."
I would not include Ada, if you're benchmarking against Rust. Mostly because Ada either outperforms it (because it can reduce sizes in the optimiser), or it is equal to it.
Weird take. I've worked in large Rust projects for half a decade (in one case, a full blockchain written in Rust). It's slow, full stop. I was under constant pressure to keep CI times down and was resorting to some of the craziest optimizations just to save a few minutes. Meanwhile, all of the platform code I was writing in Go almost never had a CI time above a few minutes.
Doesn't mean one is better than the other, but to find it odd someone had an experience with slow Rust compiliations and then blame it on their setup is a bit odd.
It is slow. Nobody would deny that. It also requires work to avoid disastrous compile times, that's also true.
But 15 minutes? Even rustc doesn't take that long (on a powerful computer), and that's a very large program that is compiled at least twice (bootstrapping)! That's just poor engineering, not knowing how to write fast-to-compile Rust code.
You can't compare rustc to a large application. Yes it's a large program, no it's nowhere near as large as Codex CLI. 1420 crates.
I just did a release build of Codex CLI with "lto = false" for codex CLI binary and code mode host only, and it took 75 minutes, of it 1063 seconds for the final rustc of codex bin. Windows Defender off. That's on a 4-core 2021-era Intel laptop with 32GB RAM and a half-TB SSD. And I can't let the build run at default concurrency, because rustc OOMs the machine, so this is with cargo jobs = 4. Regardless, the final rustc call is independent of that and that was 1063s with lto=false. Previous build was 90 minutes with default thinlto for the same CLI build.
How can Codex write fast-to-compile Rust code, do tell?? Do tell!
Behavior-wise, it does a lot more than the Codex CLI. The code of Codex is just of extremely poor quality. Just glanced, it looks like it uses at least two different HTTP server libraries (axum and tiny_http)! From quick check, rustc has 44 duplicate transitive dependencies (with different versions) while Codex has 109! And 2 dependencies are duplicated five times (in rustc the most duplicated are only three times)!
> How can Codex write fast-to-compile Rust code, do tell?? Do tell!
Like a human can, I suppose? Avoid extreme usage of generics. Avoid dependencies if not needed. Make sure you synchronize dependency versions to avoid duplicate code. Etc..
What kind of Rust projects? How many dependencies? Does it use macros? How about generics and traits?
Most of the Rust ecosystem makes unnecessarily heavy use of compile-time traits and monomorphized generics (aka Go generics or C++ templates). The language syntax encourages it as well. The thing is compile times will NEVER drop to level of Go unless the entire set of players (language, standard library, almost all library developers) actually prioritize dynamic dispatching instead of static dispatching / monomorphization. That's the tradeoff for the performance and RAM usage you gain. Template-heavy C++ also suffers from the same issues for the same reason.
Golang is fast to compile because its interface definitions are runtime/dynamically dispatched (aka `&dyn Trait` in Rust or virtual classes in C++) and it had no generics for a long time forcing types like slices, map and vectors to be magic types.
If you need fast compile times, you need to split your program into multiple binaries or write your own backend ecosystem that almost only uses dynamic dispatching. If your full Cargo dependency tree pulls 50+ crates, you'll not get fast compile times. You need to avoid derive-macros like plague. You need to use non-Cargo build systems or `build.rs` to generate Rust code instead of derive-macros.
Unlike Golang Rust also targets extremely memory constrained devices where monomorphization enables memory savings, extra optimizations and fewer CPU cycles / instructions (e.g. microcontrollers, UEFI/BIOS implementation). It is a system programming language first. With system programming languages you are responsible for these choices as the developer. Rust just happens to have a vivid scene of backend libraries since the language's safety enabled many non-system developers to write correct and performant programs quickly.
This is a bit of a tangent, but Go generics aren't monomorphized. Each instantiation is "stenciled" down to its GC shape and then the differences among types with the same shape are handled using a "dictionary" which is kind of like Swift's witness tables or Go's own interface vtables. See this description for details: https://github.com/golang/proposal/blob/master/design/generi...
The typical end result sits somewhere between monomorphization and type erasure in terms of generated code and runtime indirection.
Oh good to know and I stand corrected. I guess if you can opt-in with a syntax (like dyn does in Rust), this kind of in-the-middle generics could be a nice addition to systems programming languages. However, we need a GC-less solution of course.
> The axiom is “monomorphic data, polymorphic code”. In other words, unlike languages like Haskell and OCaml that generally rely on uniform representations (boxing) to enable polymorphic code generation, data layouts will stay the same under polymorphization, and only function ABIs will change. [...] Go’s gcshape is somewhat analogous to our concept of data layouts.
> A whole program is compiled in a single step, without intermediate linking. This makes incremental development much slower for large programs but enables far more optimization opportunities. We are actively working on build caching so that at least part of the work does not need to be redone on every compilation.
I've seen three outages at various orgs that were prolonged due to compile times. In these cases it was a root contributor to missing RTO. The compile times are a liability.
One proposal I came across actually suggested breaking up a perfectly fine monolith into a dozen microservices to reduce compile times (at the cost of cumulative CI costs going up).
When the alternative is massively rearchitecting your systems, Zig and Go begin to look really nice by comparison.
Maybe the proposal you saw was breaking up into crates, which is Rust's compilation units and can be parallelized. They're not microservices, they're just libraries which still combine to produce a single binary.
Zig dumps core and Go is slower and needs more memory though, and this matters, too. (not that Rust build times are really a price to pay for avoiding these things, it's more a result of not making fast builds a goal from the start)
> it takes over 15 minutes to rebuild trivial changes on a top-shelf CPU
I have no idea how this could possibly be this bad given that I worked full time with Rust from 2015 to 2023 on a $600 Clevo laptop from 2012 and was used to incremental builds in the low double digit seconds for projects ranging between 100k and 600k loc. (From scratch release builds where excruciatingly slow, but these only happened once every rust updates).
You only clean the build directory once every Rust update? Unless you use nightly, I don't see how that's feasible. Having a 1TB (and pretty free) disk, I still need to clean regularly (not that commonly, but more than once a release).
Both (and memory) are a problem. Build sizes is definitely more easily solved just by throwing more disk at it though (unless you are using a hard constrained mac...).
A good 4TB SSD was $1000 the last time I checked. The "throw more resources at it" attitude makes all but very top tier equipment kinda useless for many cases.
I had to abandon several projects until I bought a significantly beefier computer because of disk space, which is ridiculous. A lot of it was rust target directories taking up tens of gigabytes.
Yes it's a problem. Not dismissing it. Just a less expensive problem than CPU or RAM is all I'm saying. It's been a thorn for me too (being on a macbook myself, but now I have a dedicated build box).
I have a whole section in my global AGENTS.md about how python sucks to influence agents to not use it for anything performance sensitive.
My favorite improvement example was something a bit of a slog was taking 20 minutes in python, I told the agent to rewrite it in rust and the result was 300,000 times faster... 20 minutes to ones of milliseconds. Obviously not just a python problem but better solutions just seem to fall out of Rust and Python is just slow.
Funnily enough, last week I was working on parsing some json. I wrote a little script in python, tested it and then ran it over the main dataset. It took so long that I rewrote it in C# before the python script had finished!
I was already using orjson which is pretty much a straight 50% speed up. Calling into native doesn’t really bother me as long as I don’t have to pre compile the whole thing myself.
My experience has been that rust is significantly slower to compile than C++, by the way
It is the usual mistake to think that it does not use Python as intended (offload number crunching parts into C/C++/Fortran code eg numpy is older than many here.
I can deal with large builds, what I cant deal with is time. My rust web application takes 20 minutes to build in release mode on a high end developer machine. I can’t build it in ci/cd.
20 minut definitely sounds suspicious. For comparison, Rust compiler takes ~10 minutes to build in release mode (in the simple config without all the fancy PGO stuff we run on CI).
Do you have some more info about the codebase, is it public?
There's nothing public, but it's a SaaS application written with actix-web, askama and sqlx.
One of the big slow downs comes from a single crate (async-stripe). In retrospect, I should just use reqwest and serde to do everything with Stripe's API manually and not use any crate at all (especially if it cuts the build time in half).
I see, thanks. I assume `async-stripe` is auto-generated from some IDL, those crates sometimes bump into some pathologial pattern, I'll check it out.
`actix-web` itself should be fine, `sqlx` can be heavy if you use its compile time features, I'd assume `askama` is heavy as well, just based on the description (it generates rust code at compile time).
Otherwise it depends on how the code is structured (e.g. how generic it is, how deep types or complicated futures it creates).
Yeah, there are some things I've done to mitigate this, especially for the dev loop. Moving templates out of rust (losing some safety). Splitting the application into vertical slices. Only generating code that's changed. Turning features into crates.
The Rust toolchain by default produces a lot of debug information for each crate being compiled and the language relies heavily on monomorphization (instantiating a different function/type for each combination of generics) which translates to generally having duplicated functionality where the only difference in the object code is the different types. These two things combined, along with large dependency trees and that "features" for conditional compilation cause the same crate to be considered different across different workspaces (meaning the rlibs that were created for one can't be reused for another), means that the target directory for Rust projects can quickly grow. That, + clippy not having stabilized an already implemented process to automatically delete files from the target directory when they are unlikely to be needed again (which can't be 100% accurate, it uses heuristics because a developer could always potentially need an exact rlib from 6 months ago, it is just unlikely) means that you can expect that under the default configuration your target directory ends up being huge after a short period of time.
A lot of those things are in different stages of being fixed. I would personally want the default for generated debug information to be changed. cargo gc will help with running out of disk space. Better cross-project reuse of existing rlibs could be accomplished with some better tracking of optional features that can be unified (think having `serde` with feature `derive` enabled in one project, and having it without it in another: features are supposed to be purely additive, so you could reuse the earlier rlib on the later, but in practice people (ab?)use features in non-additive ways so it isn't straighforward to do something like that by default). Tools like hakari lets you create workspace-unified Cargo.toml to emulate this behavior.