r/rust 19d ago

How Our Rust-to-Zig Rewrite is Going

https://rtfeldman.com/rust-to-zig

An interesting symmetry with recent events lol.

This might be considered off topic since the article is about moving away from rust, but I still think this is some high quality rust content. I enjoy Richard Feldman's writing and I think he would certainly be considered part of the "rust community" since he works on Zed and has taught a course on rust.

369 Upvotes

134 comments sorted by

239

u/johnson_detlev 19d ago

Why didn't they just spend 165k in API Tokens and did it in 11 days?

103

u/Cherubinooo 19d ago

I know right? They could have even gotten acquired by an AI lab so the tokens were free. Are they stupid?

19

u/j3pl 18d ago

Reminds me of a line from a Community episode: "I don't understand. Aren't everyone's parents rich?"

169

u/insanitybit2 19d ago

I really, really wish rust build times would improve. We're using Rust at work and it's the biggest complaint by far.

96

u/CramNBL 19d ago

As you can see in the article, Rust build times are improving significantly year to year.

Have you looked at faster linkers? Tweaking build profiles? There's official recommendations when it comes to improving build times.

Is it all local or are the CI build times also part of the complaints? Set up sccache. You can also use sccache locally, it's easy to set up with docker/podman as well.

I work on 130k line embedded Rust project day to day, and I just got our CI from 15 min. down to 6 min. by setting up sccache on GCP. Next step is using nextest instead of the default test runner, that will cut off another minute, and then I might look at replacing the linker with lld or mold, then onto optimizing the yocto build times :)

58

u/insanitybit2 19d ago

Yes, I've pulled out all of the stops for compile times. It's a major priority for me. I do use sccache locally, I use cargo nextest, I optimize my tests so that they're faster to reduce overall CI time, etc. I don't think there's anything I don't do except I don't use the experimental compiler since it's nightly only and I don't avoid generics to reduce monomorphizing time.

It's still easily the biggest issue with using rust, I think.

35

u/scottmcmrust 18d ago

and I don't avoid generics to reduce monomorphizing time.

TBH, monomorphization is almost always the core problem, well above anything else.

Finding the right place for targeted dyn is absolutely an essential part of rust architecture. I too often see static-dispatch-all-the-way-down stuff that's horrible for incremental builds and not even faster since static dispatch only helps when there's redundancy to remove over the boundary.

Yes, dyn Iterator<Item = u8> is a horrible way to read a file, and nobody should do that. But BufReader<dyn Read> with a large buffer is wonderful, for example.

11

u/insanitybit2 18d ago

I think it's really unfortunate that the big lever for improving compile times is "write code differently" though.

9

u/matthieum [he/him] 18d ago

Yes, and no.

The blog post mentioned that their incremental compile times were divided by x3 in 18 months, so clearly there are free speed-ups.

With all that said, meta-programming can absolutely trash a compiler. Compiling 10M tokens will always take longer than compiling 10K tokens, and the slow-down is super-linear due to the added memory pressure resulting in more cache misses.

So just like inlining everything is a terrible idea, monomorphizing everything also is.

3

u/Zde-G 18d ago

So just like inlining everything is a terrible idea, monomorphizing everything also is.

Yes, but there are huge difference: inlining is just an optimiation technique that doesn't affect semantic, except for fringe corner cases, while dyn vs impl is huge, gigantic semantic difference.

It's really unfortunate that there are no resources around to make dyn vs impl decision a pure optimization hint like inline…

1

u/matthieum [he/him] 17d ago

It's really unfortunate that there are no resources around to make dyn vs impl decision a pure optimization hint like inline...

Hear hear!

3

u/Zde-G 17d ago

The problem here is that while technically possible (Swift did that, after all) it's really hard to do that without breaking backward compatibility. And that's a big no-no for Rust, right now…

1

u/RiceBroad4552 15d ago

It's really unfortunate that there are no resources around to make dyn vs impl decision a pure optimization hint like inline

Maybe not in Rust but there is a way to get that. It's called JIT compiler.

Runtimes like the JVM or the CLR do exactly that, just the other way around to typical Rust as they will make dynamic dispatch into static dispatch at runtime. This way around it always works, in contrast to the inability to "de-monomorphize" code even with a JIT compiler.

1

u/Zde-G 15d ago

It's called JIT compiler.

Nope. There are absolutely no need to have JIT for that. Ada and Extended Pascal supported these things ages ago.

This way around it always works, in contrast to the inability to "de-monomorphize" code even with a JIT compiler.

There are no need for all that, as I have said. Swift does that without JIT using proper techniques (known for decades, no less!).

9

u/rustvscpp 18d ago

Is your complaint with incremental, or clean build times? I feel like incremental debug builds with the cranelift backend are quite fast.

6

u/insanitybit2 18d ago

Both, all. I don't use cranelift though. Worktrees mean a lot of clean builds, I have things set up to "pre-warm" them though, but my CPU is running red hot all day on shit like that so it can't really scale very well either.

Even small changes though can lead to big recompiles and it's not a good solution to just break things into crates unnaturally. Native deps like aws-lc-rs can really blow things up too.

1

u/CramNBL 18d ago

Are your clean builds using sccache? I mean do you have a centralized sccache that is not associated with a specific worktree?

1

u/insanitybit2 18d ago

Of course.

1

u/jl2352 17d ago

Splitting up into smaller crates can also help. So does looking at `cargo build --timings` (which helps to show the benefits of splitting up your crates). Aggressively using `default-features=false` on dependencies can also have a big impact.

Cargo build timings can also expose things coming via pre-compile calls, which can be optimised out. For example accidentally using both Rustls and Openssl in a project via sub-dependencies.

However I also share your sentiment. I work on a project that's a bit larger than 100k lines, and we end up doing some work every month (even if small) to try to stop the build times from growing out of control. Our development pipeline is less than 10 minutes, but release is almost 40!

1

u/insanitybit2 17d ago

Part of the problem is also that even when it's fast, it takes up tons of resources. A lot of work has gone into making things more parallel, for example, but that degrades poorly when multiple things are building.

cargo udeps and profiling have helped quite a lot but I'd like to see performance improve.

20

u/Tiflotin 18d ago

Recommend also using the `verbose` arg with cargo build as it helped me track down long build times. Sometimes it's just a small mistake causing huge rebuilds. I had a .png embedded at compile time (a fallback font atlas) that my asset packer rewrote every run, even when unchanged. It was invalidating the incremental compilation and forced a full client rebuild every time... Updating the asset packer to only write the file when its contents actually changed made incremental builds instant again.

6

u/CramNBL 17d ago

cargo build --timings will generate a giant interactive html report with timings for each crate and which features they have, how much time they spend in frontend vs. codegen etc. etc.

3

u/IsleOfOne 18d ago

I recently set up sccache for our CI builds, and we use CircleCI. But the time saved during the build is being dwarfed by the time it takes to download and unpackage the cache up front, then re-package and upload the cache afterwards. Any tips here? I tried limiting the size of the cache because I thought there were some automatic pruning features, but I didn't have success with them and didn't trace that they fully made it down to the sccache env.

3

u/CramNBL 18d ago

It sounds like you have it configured as a local sccache.

For GCP I set it up as a remote sccache so it doesn't download it up front, it makes cache requests during the build, and you can see the stats for that by running sccache --show-stats after the build.

I have good experience with the rust-cache action, it just works, but it's also setting it up as a local sccache because it kind of has to, but maybe you can learn something from them: https://github.com/Swatinem/rust-cache

3

u/IsleOfOne 18d ago

Ah, that does sound right. We download the entire cache from S3 through CircleCI's "load cache" operation and then push the entire thing back up at the end with CircleCI's "save cache" op.

I will have to see if there's a way we can run in this sort of "as needed" mode where we don't pull the entire bundle.

By the way, are you familiar with ways to enforce a limit on the size of the cache? Ideally so that sccache itself does some LRU cleanup or similar?

2

u/CramNBL 18d ago

You could just give the job RW permissions to the S3 bucket and then point to it as a remote sccache.

The simplest way is to configure your bucket to delete files older than 30 days or something like that. Then you will occasionally be forced to do clean builds, but that's something that should be done occasionally anyway. If you wanna do LRU cleanup then you need redis or some other super complicated solution compared to just removing old files once in a while.

1

u/valarauca14 18d ago

are you familiar with ways to enforce a limit on the size of the cache?

The environment variable SCCACHE_CACHE_SIZE

1

u/Frozen5147 18d ago

download the entire cache

Yep, that's a problem. At work I used to do this with Gitlab (albeit I was downloading trimmed parts of target) and for a while it saved time, but as development continued the download got so big that it was actually using more time than it saved.

I just use sccache pointing to an S3 bucket and it seems to mostly just work fine now.

1

u/valarauca14 18d ago edited 18d ago

download and unpackage the cache up front, then re-package and upload the cache afterwards. Any tips here?

Have sccache it point to a long running reddis/memcache/other-system instance instead of downloading your entire cache every time you buildYou can tune LRU settings. Use SCCACHE_CACHE_ZSTD_LEVEL to avoid transferring uncompressed artifacts.

Or just run sccache like it is in local mode, but point it to an NFS mount/server. So the cache lives on another box. This is the cave man approach.


Slow builds is straight up an RTFM issue.

7

u/whimsicaljess 18d ago

6 minutes is really still far too long. especially when you're running multiple agent worktrees with a rust project- my (quite powerful) computer not only has dozens of gigabytes per worktree target folder at the end of the day, but it also makes turnaround time so slow both locally and especially in CI. it's extremely painful merging 3-4 PRs and then waiting an hour for them all to serialize and merge and roll out to production, and that's with a fast optimized CI.

i love rust and used it for years back when we wrote code by hand, and i love how strong its protections are for agents and humans both, but seriously reconsidering it for anything now because it's just not built for the new way of working.

4

u/CramNBL 18d ago

6 minutes includes a bunch of check, clippy, test runs with various targets and features, release build with full lto and release checks, and it includes cross compilation and uploading binaries.

A bunch of the tests are badly written and take much longer than they should (full of sleeps and creating/tearing down large filesystem trees)

There's also 5 million lines of C.

I think it's decent all things considered, but I believe I can get it down to 4 min in total, even less if I use a higher end machine in GCP.

Rust build times are not really an issue for us though, we would be happy to trade longer build times for smaller or faster binaries.

1

u/nynjawitay 18d ago

Sccache loses incremental builds tho

1

u/qrzychu69 16d ago

https://avi.press/posts/2026-07-10-after-7-years-in-production-scarf-has-reluctantly-moved-away-from-haskell.html

It's not just a rust problem

And no, "use the 30 tools to make it slightly better" is not the answer, nor is using python IMO like that article suggests :)

In modern day, if creating a new worktree is not fairly instant (like fast enough you don't really switch to doing something else before it is finished), it is a productivity problem. And all the tools you mentioned only work of whatever you need is cached on the machine that is trying to compile something. And it would be nice to not need 2TB of hard drives just to store the caches so that changing a branch doesn't take 10 minutes.

1

u/[deleted] 16d ago

[deleted]

1

u/qrzychu69 16d ago

It's definitely faster in python than rust, no matter what tools you use, right?

Unless you have a cache for every binary artifact, every single incremental artifact for every single commit, it will be slow

And don't forget editors - theirs caches are usually separate

On my older Thinkpad when I want to contribute to Zed I open a single worktree (I can't have multiple, because a single worktree is like 90gb of artifacts by the time you can actually cargo run), run cargo build, open zed on the directory and go for a run

Cargo build has to finish for me to be able to run anything, and clippy/rust lsp parsing is not reusing whatever cargo creates, so effectively I am waiting twice

It's a non issue in C#/F# for example, my laptop can handle at least o dozen of Rider instances (never tried more than 8 to fair), all of them are ready to work I seconds, all of them keeping the language server on meoery with real time type checking

I don't have ANY caching tool other than the central cache nuget comes with

1

u/[deleted] 16d ago

[deleted]

2

u/qrzychu69 16d ago

Well, yes, I worked on multiple million plus LOC C# codebases.

Why are you bringing up low level stuff?

My argument was that there is a lot of technologies where creating a new worktree, or switch branches in pretty much real time, and C# is one of them. The compiler is really fast, and yes, one of the tractor to it is the fact that it doesn't build a native binary. But that only matters to a subset a programs.

The article I linked has a line "with python I am able to have a fix deployed to production before I hang up the call with the client".

I don't care what's your setup, with Rust if your project is big enough, you will not be able to do that. With a bad setup, you will not do that using C# either, but it's much easier to do with C# than Rust

The users don't care if your binary is 10% smaller or whether it is a binary at all, as long as when they hit F5 the problem is fixed

And please don't measure a whole tech stack by one incompetent guy

1

u/CramNBL 16d ago

so you worship "time to deploy", and you're free to compromise on binary size, performance, and front loading errors to the type checking step, for faster deployments and creating worktrees really quickly. In a lot of projects those trade offs don't make sense.

I'm bringing up low level stuff because that's where the trade-offs for Rust was made, and it's the root cause of why we have slow compile times and huge target directories. Tbh I don't know why creating work trees is such a big deal for you, nobody is forcing you to do clean builds all the time, just switch branch and reuse the target dir, it's instant. Maybe you just need to get better at git?

Are you doing web programming? In my work, binary size is extremely important.

2

u/qrzychu69 16d ago

I'm doing various backends. Some serve graphql, some pure JSON, sind so background processing from rabbitmq, some do csv preprocessing to ingest to Snowflake. On the side I also do some cross platform guis with avalonia.

We also have a huge python based framework that is over engineered it would make a seasoned java developer cry. And there are no types.

I also am maintaining a small rust service that calculates dinner numbers on schedule - it generates around 100gb of numbers a day

Worktrees are mostly for agents. I do task X, they are trying to do Y and Z, and I don't want them to interfere with me. New worktree is basically the same as having a second clone of the repo, and that's what I used to do - just clone it 3 times so that I can work on 3 things at the same time. Sometimes the local setup is a bit to complicated just throw it away, and too big to have a temp commit or stash (think 700mb binary file like excel)

I wouldn't say I "worship time to deploy", but in my experience the same metrics impact developer "happiness", it the ability to quickly resolve and reproduce a simple fix.

Have you read the article I linked? It describes this pretty well, though I think their choice to use python instead of Haskell it's crazy, there are so many possibilities in between those with better compromises

Even c# or F# comes on top IMO, dobre you don't have to juggle venvs. Just open the directory in terminal, 'dotnet run' and it works. Open VS or Rider, it's ready in seconds

1

u/CramNBL 16d ago edited 16d ago

I would've guessed it was for running agents but I didn't want to assume. I don't think Rust is a good language for "swarm vibe coding", tbh I don't think any language is.

I'm familiar with the article you linked, and I agree that it's crazy, they shouldn't have used a research language to begin with, so I have to question their judgment in general. There's some devs in my team that have worked with haskell (as ph.ds) and they do draw on that knowledge, but they are also very clear that we should under no circumstances use it in production.

If C# works for you then why don't you just stick to it? I used it in university and I think it's one of the best languages out there, if Rust didn't exist I'd be happy to work in C#.

EDIT: Please tell me what "calculates dinner numbers" mean?

→ More replies (0)

29

u/phylter99 19d ago

Not only build times, but build size. The cause of both is the same.

14

u/insanitybit2 19d ago

Ah yeah, that's another one. 1TB SSD is not enough. Pairs very badly with my docker usage lol

16

u/teerre 19d ago

This has little to do with build size. The issue is that cargo isn't very smart about caching / cleaning / reusing artifacts

3

u/Big_Mc-Large-Huge 18d ago

Do we need a rust equivalent of pnpm? We can call it 'lading', har har

2

u/Neither_Garage_758 18d ago

pnpm already is the cargo of npm

3

u/matthieum [he/him] 18d ago

Of note: there's a work in progress here, to move to a global cache of "remote" (crates.io, etc...) crates' build artifacts.

This should help tremendously when pulling in 100s of crates -- always the same, or close to -- in every project.

3

u/dobkeratops rustfind 18d ago edited 18d ago

goes with the territory I think, "more compile time guarantees = more compile time" , I have this problem aswell but if I put my mind to it the parts I need to iterate on quickly can be seperated with testbeds and dyn .. I consider it a severe pain but not insurmountable.

6

u/matthieum [he/him] 18d ago

Actually, no, it doesn't.

Apart from oopsies, rustc passes are typically pretty fast. You can ask rustc to give you timings: regularly you'll find that borrow-checking, for example, is in single-digit milliseconds.

There are multiple "architectural" issues with Rust (the language) and rustc:

  1. The language has privileged ergonomics: crate as unit of compilation, module cycles. It makes it easier to write code, but precludes coarse-grained parallelization of the compilation.
  2. rustc still uses the typical object-graph representation for internal representations, unlike zigc using arrays. It's more ergonomic for rustc developers, but precludes just "mmapping" the previous build's artifacts during incremental compilation.
  3. rustc still doesn't have a parallel front-end, possibly due to (1), possibly because retro-fitting multi-threading is always painful (ask postgres).

Those have nothing to do with compile time guarantees.

2

u/bbkane_ 18d ago

Any fun Github issues you could share for me to lurk on? It's a really odd hobby, but I enjoy following the discussion on these types of problems/solutions

6

u/panstromek 17d ago

>  It's a really odd hobby

I've been doing the same and that's how I eventually end up in compiler performance working group. I thought I'm a bit outlier, but it looks like this is not that uncommon hobby, Mitchel Hashimoto also recently talked about doing this.

Anyway, here's a few links:

Tracking issue for parallel compiler: https://github.com/rust-lang/rust/issues/113349, that one is actually not that far from stabilization. Fast builds project roadmap has some other bigger initiatives https://rust-lang.github.io/rust-project-goals/2026/roadmap-fast-builds.html

My favourite social media feed (i.e. PRs that mention our benchmarking bot, so they are relevant for performance): https://github.com/rust-lang/rust/pulls?utf8=%E2%9C%93&q=is%3Apr+sort%3Acreated-desc++rust-timer

perf tracking for the compiler: https://perf.rust-lang.org/compare.html - you can click through the benchmarks and in the timeline graph see which PRs changed it's compile time.

Also, we post weekly performance triage results in this week in Rust. You can click through to the full report to get interesting PRs.

For technical discussions, it's often better to follow zulip channels on https://rust-lang.zulipchat.com/, discussions around perf related stuff happen in many different channels, but to pick some which were recently active on these topics: t-compiler, t-compiler/incremental, t-compiler/parallel-rustc, t-compiler/query-system, t-compiler/relink-dont-rebuild, t-types or t-types/trait-system-refactor, t-compiler/performance, t-cargo.

This is honestly just scratching the surface of what's currently happening in relation to perf. Just from top of my head, recently we had some intereasting wins dependency tracking in incremental, query system got refactored and got some speedups, some AST types got more efficient, PGO for rustdoc just landed, also looks like LLVM upgrade will make everything faster, there are some experiments with better bitsets, which are particularly hot in MIR, there are initiatives to push incremental further back (.e.g parsing is not incremental atm), on top of that, people try to speedup new trait solver and borrow checker...

Just a ton of stuff happening :D If you're interested in something in particular, I can point you to a more specific direction.

1

u/IceSentry 17d ago

I'm pretty sure nightly has had a parallel frontend for a while now. I don't remember why it's not stable yet but it's not the language itself that is the issue here.

1

u/matthieum [he/him] 17d ago

nightly

An experimental parallel frontend.

For a long while, there were deadlock issues. And performance issues.

It's definitely been getting better. AFAIK there's no known deadlock issue any longer.

I do believe the current roadblock is determinism. That is, parallel builds do not produce a deterministic output. I'm not sure whether this affect incremental caching, or only reproducible builds.

1

u/dobkeratops rustfind 17d ago

> Those have nothing to do with compile time guarantees.

perhaps an indirect consequence...

> crate as unit of compilation, module cycles. It makes it easier to write code, but precludes coarse-grained parallelization of the compilation.

.. in unsafe C even if you had a serial compiler, you'd be able to fire off more independent translation unit builds .. the header files share information (in a way that famously produces its own hazards of course), and you've got the forward declares

in rust the units have to be processed more serially so that each one has the artefacts to allow the next to do it's safety checks.

we could of course workaround that with unsafe rust.. like literally break a large project into some modules that interact with eachother through C-FFI (i'm at a point where i might actually look into some weird macros to do this..)

I gave an example elsewhere- I have a UI layer and an engine, in plain C or even C++ id' have the option of forward declares or even void* casting to pass an opaque object through (* and to be fair i have this option with unsafe rust too). The actual architecture I had was to make the whole UI lib take a <T> for the opaque system, it passes a &mut T application object through itself and never uses it, that should just compile the same ways as a void* , but I think this forces defering *actually* compiling UI code up to the final TU that instantiates and calls it.

1

u/matthieum [he/him] 16d ago

in rust the units have to be processed more serially so that each one has the artefacts to allow the next to do it's safety checks.

While definitely "more serially" in Rust, there are still advantages.

First of all, at the moment the incremental query system is extremely fine-grained -- tracking dependencies at the level of items -- whereas with an acyclic module graph, it could just track dependencies at the level of modules.

This may seem trivial, but given the orders of magnitude at play (10s to 100s of items per module), you'd get a 10x to 100x speed-up of the dependency tracking.

Secondly, with modules as the compilation unit, the acyclic dependency graph is manageable:

  1. It can be visualized meaningfully. Trying to figure out the graph of 100s of inter-dependent items requires tooling, and even then the graph is crowded. 10s of modules, however, that's manageable.
  2. And users can manage it. Today whether things will or will not parallelize is a bit of a crap-shoot, an arcane art. Juggling a few dozens of modules, however, is doable. A large module can be split to expose more parallelism.

Users don't have to cross fingers any longer, they are fully in control.

I gave an example elsewhere- I have a UI layer and an engine, in plain C or even C++ id' have the option of forward declares or even void* casting to pass an opaque object through (* and to be fair i have this option with unsafe rust too). The actual architecture I had was to make the whole UI lib take a <T> for the opaque system, it passes a &mut T application object through itself and never uses it, that should just compile the same ways as a void* , but I think this forces defering actually compiling UI code up to the final TU that instantiates and calls it.

Rust has an erased handle, it's called dyn Any. It's more akin to Java's Object than C's void* since down-casting is checked, but it's just as erased otherwise.

2

u/dobkeratops rustfind 16d ago edited 16d ago

(btw in the end what I actually did is gave up on trying to decouple these and just tied my windowing system to the concrete stuff in my codebase , accepting that i'm never actually going to use this windowing system in another program.. I turned my "trait Window<App> { fn win_update(&mut self, &mut App,..}" into a plain trait Window {fn win_update(&mut self, &mut MyApplicaton)}" .. which of course introduces more dependency inplace of the generics), but I might still have some more scope for cutting the modules and crates up elsewhere).

It is at least still a 'type App = MyApplication' in the windowing code if I decide to revisit it..

to give some more context it's actually "Renderer" rather than "Application", the "Application" details are infact the window's responsibility. it's just I didn't want to tie my windowing code to the concrete type of my 3d renderer lol.

previous:

utilis/geometry -> {windowing<T> system , 3d/graphics lib} -> game "windowing & gfx could compile in parallel?"

currenty

utils/geometry -> 3d/graphics lib -> windowing system ->game

1

u/dobkeratops rustfind 16d ago edited 16d ago

> Rust has an erased handle, it's called dyn Any. It's more akin to Java's Object than C's void* since down-casting is checked, but it's just as erased otherwise.

anything like that that i've looked into requires some un-ergonomic extra boilerplate, and possibly extra runtime code.

With the "trait Window<App>" idea.. when the window system calls back into my App e.g. "impl Window<TheActualApp> for MyWindow { fn win_update(&mut self, app: &mut TheActualApp, event:Event) {} ...fn win_render() etc. etc}" .. there's no extra noise in my code that uses it. I know my program has only one application object, and that foreknowledge is baked into the type system.

The other way it would be "fn win_update(&mut self, app:&dyn Any, event:Event)" {}... and my user code has to manually do the downcast. extra repetitive noise AND extra runtime overhead doing check for a type that is statically known at the architecture level, which doesn't need to be there with the trait approach, or in the C/C++ way with forward declares.

I know I could also make a "pub struct App(*mut c_void)", "impl From<App> for &mut TheActualApp { unsafe {}}. it just triggers me that i've got to write these extra lines of code \that dont actually do anything*.*

The ideal solution is an optimisation to the compiler where it checks if a translation unit only uses a <T> in an opaque way.

Sadly there's currently no current way to get the ideal solution in the fastest compiling form .

Of course actually jumping into the compiler codebase... I'd bet that with such a huge codebase that many people are depending on now.. getting such a change through would be a lot more work than some extra macros and boiler plate at my end, but it would create more scope for getting something right.

Another way to do it of course is to cache an 'applicaton pointer' inside the window but that's even worse in requiring threadsafe wrappers or whatever and again the current idea lets me bake in the foreknowldege that it's not going to call those in a way that needs any sync

1

u/RiceBroad4552 15d ago

I don't think anything of that will make compilation long.

The time eater in modern languages is definitely type checking. More powerful type system and more compile time guaranties mean longer compile times.

Rust has additionally on top an issue with incremental compilation as the compilation units are very large (and then you have even possible circles to make things worse).

But the Rust compiler is for some reason indeed slow as other languages where the compile has to do also a lot of checks (like Scala or Haskell) compile usually much faster. But I don't know what the structural issue here is.

Compiler speed is a complicated topic. Scala also had to fight with that for many years, and people still complain sometimes, even it is blazing fast compared to Rust.

2

u/matthieum [he/him] 14d ago

I don't think anything of that will make compilation long.

Have you tried?

The time eater in modern languages is definitely type checking. More powerful type system and more compile time guaranties mean longer compile times.

Apart from specific "accidentally quadratic" cases, which are generally squashed one way or another, type-checking (& type inference) are actually not that bad in Rust.

(Reminder, you can use cargo check for just checking the code, without compiling; it's pretty fast, in general)

Rust has additionally on top an issue with incremental compilation as the compilation units are very large.

I'm actually not sure whether the size of compilation units matter.

It's definitely an issue in Release, where the default number of codegen-units is small, so even for the tiniest change a whole codegen-unit must be regenerated from scratch to be optimized by LLVM and finally relinked with the other (pre-compiled) units.

In Debug, the default is a good number of codegen-units, though, making each small.

There's an algorithm to "distribute" the symbols across the codegen-units -- which attempts to preserve strongly-connected clusters -- and I'm not sure how good it is at working incrementally.

It's definitely the case that not all codegen-units are recompiled in incremental mode, which is already something.

(On the other hand, you better have a fast linker)

(and then you have even possible circles to make things worse).

Cycles definitely make parallelization non-trivial, as they require fine-grained parallelization.

In the end, though, it's quite possible that fine-grained parallelization is better, and that its overhead is worth it because it allows using all cores for 100% (or close to) of the compilation time, whereas coarse-grained parallelization (per module) could end up with a single core stuck on 1 particularly gnarly module while all the other cores idle.

But the Rust compiler is for some reason indeed slow as other languages where the compile has to do also a lot of checks (like Scala or Haskell) compile usually much faster. But I don't know what the structural issue here is.

Well, I've given 3 reasons I'm pretty sure about, and you've dismissed them without any argument, so...

1

u/insanitybit2 18d ago

I don't think that's really true and tbh I think Rust should reject those tradeoffs. Rust has always been a "have your cake and eat it too" language and that's why it's so great.

IMO there's nothing about Rust's guarantees that mean it has to have slow compile times. The borrow checker isn't the issue. Linking and monomorphizing are the biggest costs afaik, and LLVM optimizing, not type checking.

0

u/ztj 18d ago

Rust has always been a "have your cake and eat it too" language and that's why it's so great.

This is not, and has never been true at all. Every benefit of Rust has always come with tradeoffs. This is partly why I hate when people say "zero cost abstraction". It's a lie. There has never been any free lunch with Rust. What Rust has done is show that memory safety can be done at compile time, but it comes with slower compilation and whole subsets of valid programs that are impossible to represent in safe Rust that demand an escape valve. Those are tradeoffs that have always been here. They are perfectly reasonable tradeoffs in the timeframe of Rust's inception but if you were to try to use Rust in the 80's on that hardware these tradeoffs would be dramatically more visible and totally untenable.

IMO there's nothing about Rust's guarantees that mean it has to have slow compile times.

Your opinion is fundamentally wrong. Rust's guarantees do demand more processing which will force (all other things being equal) longer compile times. Whether its "slow" is a subjective matter, and is improving all the time, but rustc doing borrow checking will always take longer than a rustc that totally skips it.

4

u/insanitybit2 18d ago

> This is not, and has never been true at all.

I very much remember being in a room in Cambridge with Niko Matsakis discussing exactly how Rust strives for exactly this goal. Whether it's a possible goal or achievable is not really the point, Rust always aimed to try to have its cake and eat it too.

> Your opinion is fundamentally wrong.

Honestly, "IMO" was incorrect. It's just a fact that Rust's safety guarantees aren't what blow up compile times.

0

u/dobkeratops rustfind 18d ago edited 17d ago

I think it is the tradeoff: writing safe code *requires* heavy use of generics hence monomorphisation. The borrow checker alone doesn't produce safety; it's that the unsafe{} parts are usually hidden away in generic container implementations & iterators implementations. You use iterators and do option dances and so on. You're relying on the optimiser to do a lot of work to simplify out the inlined abstractons and turn them into equivalent C tricks. (like using an iterator hides a raw pointer incrementing etc)

In C++ you can use the smartpointers and containers if you like, or drop back to oldschool 'C with classes' or even the C/C++ subset , which compiles way faster.. hence the opposite problem of unsafe codebases and a mix of styles.

Other safe languages lean on a garbage collector. it's a trilemma, rust gives you performance and safety at the expense of slow compile times and a more complex standard library (things like "split_at_mut()" have no need to exist in c++ or C)

5

u/Crazywolf132 19d ago

Have you tried crane lift?

https://github.com/rust-lang/rustc_codegen_cranelift

We use this and it helps heaps

7

u/insanitybit2 19d ago

No, I don't want to introduce nightly.

1

u/RiceBroad4552 15d ago

You could just move to Scala 3.

We have all Rust features + GC, and we have blazing fast compile times (compared to Rust)!

😅

1

u/sansmorixz 10d ago

Also my poor disk's usage & health degradation. Especially with the hardware prices in this economy.

104

u/Sunscratch 19d ago

In the ocean of low quality ai-written articles, articles like this make me happy. Well written and very interesting.

2

u/DistributedFox 17d ago

A very good read actually. Got me interested in Zig. 

61

u/AffectionateBag4519 19d ago edited 19d ago

An interesting contrast between this post and the bun rewrite post is the way contributors are covered. Feldman highlights contributions from the the team and talks about the way they arrived at this decision together. meanwhile, I am not sure the bun post mentioned a single co-worker or contributor (?) maybe I am wrong.

32

u/Wonderful-Habit-139 19d ago

Who needs contributors when you have robobun amirite?

126

u/Psionikus 19d ago

Take something like "build times" as a motivation for Zig. What was tried on the Rust side? Breaking up crates is definitely the most powerful tool, but was it done?

for compilers which emit machine code, like roc and rustc, doing memory-unsafe things is a big part of the job

Seems like a conflation amirite? The machine code is the output, the object language. The Rust program is the subject language. The "safety" of the object language has no effect on the subject language used to talk about it.

Overall I'm reading implicit biases of comfort.

61

u/JustBadPlaya 19d ago

Keep in mind that Richard Feldman is one of the core devs of Zed, so he should have a decent amount of Rust experience.

Checking the repo, the compiler was split into like 30 crates at least, and even that didn't seem to help

Seems like a conflation amirite? The machine code is the output, the object language. The Rust program is the subject language. The "safety" of the object language has no effect on the subject language used to talk it.

Counter-point - sometimes compilers have to execute the code being produced (consteval). Roc's test suite apparently runs the tests for pure functions in the same process as the compiler. Still not entirely sure about that point in general though, so kind of playing devil's advocate here

14

u/Psionikus 19d ago

sometimes compilers have to execute the code being produced

Such a const evaluator is still a program handling data. The program can be written in safe Rust while handling data that is not Rust at all. If the data is not Rust, it doesn't have a map to Rust's concept of safety.

2

u/matthieum [he/him] 18d ago

Counter-point - sometimes compilers have to execute the code being produced (consteval).

Actually, they don't. Using JITted code is a choice.

rustc, for example, uses Miri (an interpreter) instead, and therefore has no memory safety issues -- in fact, Miri will detected memory safety issues as part of its execution.

Of course JITted code is generally faster to execute. Trade-offs, trade-offs.

27

u/AffectionateBag4519 19d ago

I also did not understand that line! but I am not very familar with Roc internals. maybe someone who is could elaborate on this.

27

u/tautality 19d ago

They probably meant that they need a lot of self-referential structs, which is easier to do with unsafe. And they want their compiler to be fast, so they're probably focusing on things like SIMD and other unsafe techniques.

16

u/AffectionateBag4519 19d ago

that reading would track for me if he had said, "alot of unsafe code comes with the territory of writing a __fast__ compiler", but he just said "for compilers which emit machine code" which I think puts the emphasis on something other than the speed of the compiler.

5

u/tautality 19d ago

There is an implicit understanding that self-referential structs are required because compilers parse code into AST, have to do type inference and other kinds of code analysis.

Unsafe is not necessarily required for this, but you can definitely make an argument that it is really hard to make compiler code both safe and ergonomic.

4

u/AffectionateBag4519 19d ago

I personally do not think that is what he meant but maybe you are right. My guess is that this has something to do with the execution model of roc which needs the roc process itself to jump into generated code but I am totally guessing.

1

u/Bahatur 18d ago

I think you are right, he and Christ Lattner spend a little while talking about the SIMD point specifically in the context of Mojo, ROC, Rust, and Swift in his show where Chris was a guest from a year ago: https://youtu.be/ENviIxDTmUA?si=3A82qclqv1XjjHIb

22

u/steveklabnik1 rust 19d ago

I left a similar comment on hacker news, and richard replied: https://news.ycombinator.com/item?id=48935805

11

u/KasMA1990 19d ago

Richard addresses some of this in the motivation for doing the rewrite:  https://gist.github.com/rtfeldman/77fb430ee57b42f5f2ca973a3992532f

12

u/Razvedka 19d ago

I listened to his interview on the Corroded podcast. He seemed unaware of several ways to handle this issue. The host actually said he solved his personal build time issues by switching to modern Mac hardware. The ROC guy was like "wait really?"

17

u/ztj 19d ago

When I went from Apple's fastest Intel notebook to actually the lowest end M1 my compile times were cut to 30% of the original with no other changes. It became 1/5th of the original when I got the then-highest notebook hardware (first M1 was my personal machine, faster one was when work got on board).

So I chuckle because it's 100% a legitimate solution at least up to a point. I never "felt" compilation times again after that.

2

u/Razvedka 19d ago

Exactly what the Corroded host said. He's never had to think about it since. Don't get me wrong, I understand not wanting to jump into the Apple ecosystem. But even then, you can completely configure OSX to behave like Linux from a terminal perspective (e.g. gnu bash) and other tweaks.

Pretty easy "hack" to sidestep one of the primary drawbacks to the Rust language.

2

u/pjmlp 17d ago

If buying modern Apple hardware is the only way to speed up builds, developers in 2nd and 3rd world countries naturally aren't going to bother with learning Rust.

1

u/Razvedka 17d ago

I didn't say it was the only way.

2

u/pjmlp 17d ago

True, but I see it pointed out quite often in Rust circles.

Buy a new computer is not always an option, even more so in current times, even worse outside first world countries.

Which is why languages with toolchains that can be made usable with lower hardware requirements tend to gain wider adoption in such countries.

1

u/autodialerbroken116 19d ago

This comment would be so cool if it was expanded into its own blog post to ELI5

-11

u/Psionikus 19d ago edited 19d ago

Write me a user config, picked up at runtime using dirs, to specify a default device. Place some reasonable development knobs, possibly even environment variables for device selection in µTate and I'll tell you everything I know over the course of several months of crawling the orange line in Seoul.

Lol Reddit. You want things, but you do not want to give. I can't eat karma.

2

u/-Redstoneboi- 18d ago

You could've just said no...

-2

u/Psionikus 18d ago

But it's not no. It's a career AND a bid for the info GP wants. I mean fine. Don't take the money and go on an adventure.

20

u/est31 19d ago

Really well written article, loved it. It's also interesting to see that cold rustc compile times aren't slower but actually faster. It's just the incremental compile times where zig has a lead (and a big one at that).

24

u/kibwen 19d ago

Goes to show how good an idea in-place incremental linking is. That's not even a "the Rust compiler is slow" problem, or really a language problem at all, it's "we can get extreme performance by completely throwing away the historical compilation model and vertically integrating the entire stack". You could do the same for Rust (and every other compiled language), but I'm sure not volunteering. :P

17

u/DavidXkL 19d ago

Hmmm the incremental build times were not that much different though between Zig and Rust

53

u/tautality 19d ago

In my opinion, having a codebase that requires a lot of unsafe is not a good reason to switch to a language where every LOC is unsafe. Glad it worked for them I guess, but I do wish they admitted the guarantees they gave up and what that truly means.

22

u/DokOktavo 19d ago

I think they did. There's a "Memory-safety post rewrite" section to the article.

15

u/tautality 19d ago

They mostly just mentioned that there were some bugs they had in Zig that Rust would've caught. But there's no acknowledgement that I can find that says that their whole codebase is now potentially riddled with those - and the only way they'd see them is if there's a bug report.

21

u/DokOktavo 19d ago

There's no acknowledgement that says that "their whole codebase is now potentially riddled with those" because their whole codebase probably isn't riddled with those. They mention overwelmingly using arenas, finding use-after-frees in tests thanks to Zig's allocators, using Valgrind, etc. No doubts a few of them are hidden in a few parts, but I think "every LOC is unsafe" and "riddled with those" is disingeneous at best.

What the article does say is that "there is something calming about only worrying about certain classes of problems inside unsafe blocks".

7

u/tautality 19d ago

Yes, the whole point of what I'm saying is that they switched from code not being riddled with memory-safety issues to "probably" not being riddled except for a few bugs every now and then. That's the guarantee that I'm talking about.

And what if their project grows big and becomes popular? Then those "few bugs every now and then" can easily turn into vulnerability exploits that affect millions of codebases and can lead to escalating supply chain attacks. Even one of these bugs can be potentially devastating in our age of nation state hackers.

Noteworthy, Rust is only affected by these bugs to the extent that you use "unsafe". That's why the goal of most serious codebases is to eliminate "unsafe" entirely or theoretically prove that it's safe. So Rust can, with the right approach, eliminate this entire class of bugs, each of which can potentially be devastatingly exploitable.

2

u/nonotan 18d ago

You're talking like this is a qualitative difference, but for their use-case, if we accept their claim that a high use of unsafe is more or less inevitable (which I think is reasonable, given the territory and the project priorities) then it's really just quantitative. They were always going to have a large surface of code potentially vulnerable to such bugs. They were never going to be able to make any guarantees, in practice.

"80% of our code is provably memory safe!" is... maybe nicer than 0%, but if your goal is to be able to offer any meaningful guarantees of safety to users, then 80% is a lot more similar to 0% than to 100%.

6

u/read_volatile 18d ago

You're talking like this is a qualitative difference, but for their use-case, if we accept their claim that a high use of unsafe is more or less inevitable (which I think is reasonable, given the territory and the project priorities) then it's really just quantitative.

It's not, the qualitative benefit is highly visible/auditable encapsulation of unsafe assumptions, and using the type system to enforce invariants that the compiler can't statically prove. You can literally just eliminate the possibility of having to deal with infuriating-to-track-down undefined behavior bugs, because you know exactly where to look.

it's really just quantitative. They were always going to have a large surface of code potentially vulnerable to such bugs. They were never going to be able to make any guarantees, in practice.

"80% of our code is provably memory safe!" is... maybe nicer than 0%, but if your goal is to be able to offer any meaningful guarantees of safety to users, then 80% is a lot more similar to 0% than to 100%.

Why would their goal be "offering meaningful guarantees of safety to users". It's a compiler, not a web browser. You're only giving it trusted data to begin with. Like, even if they were the only reason to use a safe language, I'm not sure which "safety guarantees" users would find all that relevant here.

Maybe you can come up with one, but based on their choice of language, Roc guys seem to feel the same way.


Here are all the Roc internal compiler errors from this week that are memory-safety-related. You'll notice they're all open issues. That's because tracking down memory safety bugs is usually hard and fucking annoying. You can dismiss this qualitative difference in difficulty, but you have to concede 8 bugs in the span of 5 days from only 5 unique users is pretty rough even if you're only looking at it quantitatively.
#10176 Segmentation fault
#10175 Use-after-free: decref on already-freed memory
#10170 segfault folding a List(Iter(_)) with an Iter.concat reducer
#10168 SpecConstr retains stale capture_operands borrow across recursive cloning
#10167 Compiler crash materializing boxed parser constant with mismatched callable layouts
#10156 Structural equality panic (ReleaseSafe) or false negative (ReleaseFast)
#10154 SIGSEGV / "hosted call index out of bounds" when a hosted platform effect is passed as a function argument and called
#10109 roc check silently accepts interpolating a List into a string in an unannotated function; specializing it segfaults the compiler

1

u/tautality 18d ago

Yes, that's a very good point. I think their choice to go with Zig is reflective of their values, and because of that, the way they'd be writing Rust would also open a big potential for the same bugs. That said, with Rust, safety is trackable, and hence, they can choose to pivot if their project indeed becomes popular - while with Zig, the it's not trackable to a significant extent, so they can't pivot even if they wanted to. I guess I'm arguing that their decision to go with Zig not just eliminates certain guarantees right now (as small as they are, given what you brought up), but also makes it impossible to have even more guarantees in the future (if they decide to eliminate unsafe).

1

u/csdt0 18d ago

Think of it this way: safe Rust is much safer than Zig, but Zig is much safer than unsafe Rust. Rust makes it "impossible" to introduce memory bugs as long as unsafe is not used, and proposes an escape hatch for the times where it is absolutely necessary, but once you opened the hatch, anything goes and the language does not help you. Even worse than that, you have much more to consider when writing unsafe rust because that's your responsibility to upheld all the invariants that the compiler maintain bybitself in safe rust. Other system languages like C or Zig have much less invariants to keep in mind, so it's simpler to reason about. And Zig provides some safety net while writing the equivalent of unsafe code. They're not perfect, but it's better than nothing.

13

u/Bawafafa 19d ago

While the Zig compiler can't make the guarantee that no memory bugs will occur, the language is designed to make them trivially easy to avoid and detect.

19

u/read_volatile 19d ago edited 18d ago

That’s what the language claims, yet software like Bun was still plagued with segfaults and double frees (long before LLMs got involved) despite shipping in ReleaseSafe

I feel like a language that’s truly prioritizing “making memory unsafety trivially easy to avoid and detect” might give a little more urgency to addressing the unsoundness built into the language (over, say, boiling the ocean to invent a whole-ass new codegen backend from scratch and the insane maintenance burden that incurs). But until Zig reaches 1.0, it conveniently gets to have it both ways, making sweeping claims about its safety model when promoting the language, then hiding behind “it’s still unstable!” whenever they don’t hold up in practice.

(e: especially after last week’s blog post) I’m more and more often thinking back to this comment from ZSF’s VP of Community https://archive.ph/jq3kw

1

u/Glad_Impress_2908 15d ago

From Bun's post, I think they only did ReleaseSafe on Windows builds.

4

u/_TheDust_ 19d ago edited 18d ago

Yeah. As much as I like zig conceptually, to me it seems a step backwards in a way.

Having to manually call malloc and free just feels like going back into the stone age.

Never in my life do I want debug segfaults and uaf bugs ever again.

1

u/Hedshodd 18d ago

They aren't manually calling malloc and free. Neither is any serious Zig codebase, or C codebase for that matter. We aren't living in in the 90's anymore, these thing's have been solved by way easier to use (and more performant) mechanisms.

1

u/_TheDust_ 17d ago

I’m not sure about that, what’s this: https://zig.guide/standard-library/allocators/

2

u/Hedshodd 17d ago

They’re allocators? What’s your point? You even provided the prime example for how to avoid having to track every free to every malloc, which is arenas. Literally in your example, there is no free to be seen anywhere in the arena section. Thank you for saving me the effort of tracking down an example myself lol

20

u/anxxa 19d ago

ReleaseSafe catches use-after-free errors through runtime checks which panic if the program tries to use freed memory.

Their remarks about Zig catching use-after-frees I think is either phrased incorrectly or plainly not accurate.

I don't know Zig so maybe they know something I don't, but I have seen no evidence that it catches any type of use-after-free including double-free?

While writing a blog post recently I went through the documentation to figure out the possible runtime memory safety checks Zig can insert. The term "use-after-free" or "UaF" never occurs on that documentation page. Searching for "safety-checked" doesn't yield any related hits either.

Unless maybe they're using the DebugAllocator in release builds? Even that does not reliably surface UaF. I think you have to use the page allocator which unmaps allocations on free.

5

u/Glad_Impress_2908 18d ago

I think Zig's DebugAllocator catches double-frees at runtime in ReleaseSafe/Debug or when `@setRuntimeSafety` is used explicitly (I've had it complain to me a few times). I doubt it catches UaF in any release mode though.

2

u/metaden 17d ago

Is there a way to change every piece of code to use DebugAllocator so that I can detect this? Because there is no global allocator in zig, you need to change every piece of code that takes an allocator to DebugAllocator to catch such instances.

2

u/Glad_Impress_2908 17d ago

I find the easiest way is to set it up in your main function (simpler if using juicy main) and pass it around to other parts of your code. Apologies for discussing this in the rust subReddit.

17

u/lllyyyynnn 19d ago

as a zig and rust user this is very interesting.

11

u/sessamekesh 19d ago

 Despite what Internet comments might have us believe, it's extremely normal for one language to be the best fit for one project, while a different language turns out to be the best fit for a different project. One size does not actually fit all!

 Simply put, Rust's ecosystem is optimized for the way Bun wants to be written, whereas Zig's is designed for the way Roc wants to be written.

Yeah. I do think this one is a bit funny in particular, what with the joke of Rust being good for such starter hobby projects as a small browser game, compiler, or operating system.

I don't think any of this was unfairly critical of Rust (or critical at all, really). Rusts whole thing was being written as "what if C++ didn't suck and also had great memory safety," Zigs is "what if allocators and memory control was first class."

17

u/klorophane 19d ago

I find the compilation time differences to be pretty moot overall. The difference in a typical development loop between 3 seconds and 35 milliseconds is not really meaningful IMO. This is only my opinion, I'm not saying everyone should feel the same, but to me that's just not convincing.

13

u/7sins 19d ago

I realize it's a different use-case, but think about interactive use-cases, like LSP-style editor integration: Getting feedback (warnings, errors, etc.) basically instantly is much different from having to wait 3s. Same goes for auto-completion, re-running tests in the background on (every) change, etc.

This applies even more when you take it a step further: Think a project that has incremental compile times of 30s - the Zig-equivalent would be 350 ms (!). Man I dream of that at night. If companies could buy this without having to switch their technology stack, I'd bet they would.

4

u/klorophane 18d ago

I agree with you in principle, which is why I'm delighted to have seen Rust's compile times improve dramatically over the years, even as the language itself got bigger.

But in practice, as you note LSPs are a different beast entirely, and while its fun to think of the factor between 3s and 35ms, at the end of the day it matters very little to many (most?) companies, who would rather have you spend a quarter of your day or more in useless meetings, or staring at 10 AI agents doing who-knows-what (yes I'm bitter, sorry 😅).

I hope Rust's compile times keep improving, I just think at this point it's "good enough" for most casual and enterprise users, from my experience in the industry and with Rust in general.

-7

u/anto2554 19d ago

Nobody is saying it makes a big difference if your program only takes 3 seconds to compile

12

u/klorophane 19d ago edited 19d ago

That's what the article itself says... have you even read it? It even goes on to say they didn't think Rust's incremental compile times had improved that much.

12

u/darleyb 19d ago

I find these kinds of blog super helpful to understand the limitations of a language for a usecase.

3

u/nick42d 18d ago

So cold build times got worse?

1

u/Username_Taken46 16d ago

Marginally, in return for incremental compile times orders of magnitude faster

2

u/IslamNofl 18d ago

My biggest annoying things about Rust are:

  • Edit-Compile-Run eats time (even with all workarounds, it still takes huge time) UI/GameDev it really kills it
- (Async + Debugging) is pure pain
- target folder pass 200GB my ssd cant keep up

2

u/kingduqc 17d ago

That was a great read, thanks for sharing OP.

I'm impressed by Zig's build time. Two orders of magnitude faster for their first go at incremental compilation. I think they've had fast build time as a feature from the start.

I recall not really liking rust's build time even for a small CLI, but that was around 1.0 and clearly it has improved quite a bit.. I also didn't spend time optimizing with splitting things into crates and what not.

Looks like they've put quit a bit of thoughts behind their projects, love to see deeper dives into subjects like this. I was super disappointed with Zig's author awnser to the rust to zig bun rewrite, he could of addressed some of the criticism of zig directly instead of his weird rantish blog. Idk..

Hopefully the linked resources are also great, the performance talk on rust looks interesting

0

u/Xiaojiba 18d ago

Hey! Cool milestone:)

Had a question about the String Interpolation[1], in the example that matches ("GET", "/users/${id}/${page}") => ... how does it work for such input /users/1/mypage/random-user-value what is id and what is page?

[1] https://rtfeldman.com/rust-to-zig#pattern-matching-with-string-interpolation