Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Zig vs. Rust at work: the choice we made (ludwigabap.bearblog.dev)
94 points by qouteall on June 20, 2024 | hide | past | favorite | 54 comments


> "WASM support was just as smooth in Rust as in Zig".

There are still way too many Rust dependencies that don't support WebAssembly, and even functions in the standard library that compile but will crash at runtime on WebAssembly. And if a dependency requires C code, `cargo-zigbuild` is required to compile it anyways.

My experience with Zig is that pretty much everything compiles out of the box to all the supported targets, and works the same way. This includes code using SIMD.

I also like the fact that the code can be optimized for the runtime's feature: adding -mcpu=baseline+simd128 compiles everything for wasm runtimes with support for SIMD, including the C library ("wasi-libc"). This is not specific to the wasm target, but the fact that even the libc is recompiled with the same optimization flags as the rest of the code is neat. You can really optimize the code for speed, size or a specific target.

WebAssembly modules compiled from Zig code are also very small and memory efficient, without being forced to use a "[no_std]" mode. That can make a big difference if you have to distribute them, or are billed according the resource usage.

> The Zig build system is amazing and 100x better than our makefiles.

This.

People often think Zig is just a language. But it's a toolchain, that can be used even for code not written in Zig.

It's a fantastic replacement for makefiles / CMake / ccache / hacky shell scripts, with seamless cross-compilation and webassembly support.


I have written some rust I like it. I prefer GO for day to day but I wont turn my nose up at Rust...

Cargo on the other hand... I saw someone say "Rust is turning into the language that tokio ate" ... and this is a cargo shortcoming.

Zig dependency management feels more Go like, and in my book thats a good thing. I like decentralized.

Zig is going to be what I build my next "fun" project in. Im actually excited to spend that time when I find it. I haven't felt that way about something new in a long time.


This feels very shortsighted to me.

"Easy to learn" and "easy to hire for" are an advantage in the first few weeks. Besides, we now have data indicating that ramp up time in Rust is not longer than in other languages.

On the other hand, serving millions of users with a language that isn't even v1 doesn't seem very reasonable. The advantages of a language that is memory safe in practice and also heavily favors correctness in general boosts productivity tenfold in the long term.

I'm speaking from experience, I switched from C++ to Rust professionally and I'm still not over how more productive and "lovable" the language in general is. A language like zig isn't bringing much to the table in comparison (in particular with the user-hurting decisions around "all warnings are errors, period")


> The advantages of a language that is memory safe in practice and also heavily favors correctness in general boosts productivity tenfold in the long term

As someone who has some large projects in C++ and co tribute to OSS C++ projects I find this isn't true. The big "productivity" boost I saw when using rust for some projects was that there were good rust wrappers for common libraries or they were rewritten in rust.

In C++ using the C API directly is "good enough" but because there is no nice wrapper development is slower than it should be and writing the wrapper would be significantly slower unless you expect it to be a decades long project.

When I'm not needing to build my own abstractions above a C library in C++ I find it just as productive as rust and the moment I need to touch a C library in Rust I feel even less productive than C++.

There is definitely an argument to be made about correctness in large teams being beneficial in the long run but clearly very large projects are able to keep some sanity around keeping developers in check but this is the one metric where rust has a leg up on C++ and big backers of C++ agree that it's the place C++ is sorely lacking. Every other metric isn't worth discussing unless this is fixed.


> As someone who has some large projects in C++ and co tribute to OSS C++ projects I find this isn't true.

Well, that goes contrary to my personal experience (professional dev in C++11 and up for a decade), and also to the data recently shared by Google[1] ("Rust teams are twice as productive as C++ teams"). Either your Rust is slower than average, or your C++ is faster than average. Perhaps both.

The reasons for being more productive are easy to understand. Build system and easiness to tap into the ecosystem are good reasons, but tend to diminish as the project matures. However, the comparative lack of boilerplate (compare specializing std to add a hash implementation in C++, and deriving it in Rust, rule of five, header maintenance and so on), proper sum types (let's don't talk about std::variant :(), exhaustive pattern matching, exhaustive destructuring and restructuring makes for much easier maintenance, so much that I think it tends to an order of magnitude more productivity as the project matures. On the ecosystem side, the easy access to an ecosystem wide serialization framework is also very useful. The absence of UB makes for simpler reviews.

[1]: https://www.reddit.com/r/rust/comments/1bpwmud/media_lars_be...


May also be the type of projects I would reach for C++ in as well.

I generally use golang for anything "high" level. I reach for C++ when I actually need that level of control and to be honest most of the ergonomic features in Rust are much higher level than what is needed for proper systems development.

I think my big productivity issue with rust has always been the very weird hoops I need to jump through to make it do stuff I can confirm is correct but the borrow checker prevents me from doing. I can imagine many use cases where Rust would be significantly more productive than C++ but those are places I wouldn't use C++ for in the first place.

Regarding serde, yes it's amazing but also blows up compile times, I know that's rich coming from the C++ camp, but realistically it's not great. I also find rust-analyser painfully slow but that's equally true with clangd except not for speed but more that clangd still doesn't support modules 4 years after they were standardised...

There are many issues with C++, but the reality is there are many issues with any given language and the tradeoffs I need to make with C++ feel better to me than the rust tradeoffs.

And regarding the google report, was that not self reported productivity. Also on a much smaller codebase? I did say for extremely large codebases rust has some very clear advantages and even strong supporters of C++ will agree there ( see any modern Herb Sutter talk, Microsoft, or the reports from Google) but I'm pretty sure we have learnt the lesson that what works for Google or Microsoft or Meta may not work for everyone.

Just make an informed decision is my point, you have tradeoffs for each laguage and for me easy C interop is extremely important for the places I actually need C++. For the rest I use golang.

> The absence of UB makes for simpler reviews

Rust also has UB and you should still be runnig fuzzers and sanitizers on your rust code, that is true for C++. Yes rust reviews are easier, but there are tools available that should be run on CI that can catch those issues, likewise with coding standards. It's not the perfect solution but it's the one we have.


> Rust also has UB and you should still be runnig fuzzers and sanitizers on your rust code, that is true for C++.

Safe Rust doesn't have UB[1], and safe Rust is what I review 99% of the time. For unsafe modules, you should indeed be running sanitizers. Fuzzers are always good, they are also interesting for other properties than UB.

> tools available that should be run on CI that can catch those issues

Available tools have both false positives and false negatives. Careful review is unfortunately the best tool we had in C++ to nip UB in the bud, IME.

> I think my big productivity issue with rust has always been the very weird hoops I need to jump through to make it do stuff I can confirm is correct but the borrow checker prevents me from doing

Interesting, I remember having to adapt some idioms around caching and storing iterators in particular, but very quickly I felt like there wasn't that many hoops and they weren't that weird. There's a sore point for "view types" (think parsed data) that are hard to bundle with the owning data (I have my own crate to do so[2]), but other than that I can't really think of anything. Do you mind sharing some of the patterns you find are difficult in Rust but should work, in your opinion?

> [rust-analyzer and clangd]

I find there's been tons of regressions in usage in rust-analyzer recently, but IME it blows clangd out of the water. The fact that Rust has a much saner compilation model is a large contributing factor, as well as the defacto standard build system with nice properties for analysis.

clangd never properly worked on our project due to our use of ExternalProject for dependencies.

> And regarding the google report, was that not self reported productivity.

No, the recent report (presented by Lars at some Rust conf) is distinct of the blog article and is not self reported productivity. They measured the time taken to perform "similar tasks", which google is uniquely positioned to do because it is such a large organization.

> Just make an informed decision is my point, you have tradeoffs for each laguage and for me easy C interop is extremely important for the places I actually need C++. For the rest I use golang.

That's fair. I would say the tradeoff goes very far in the Rust direction, but I have strong bias against golang (I find it verbose and inexpressive, I don't like that it allows data races[3])

[1]: to be precise, if safe Rust has UB it is a compiler bug or a bug in underlying unsafe code. By safe Rust, I mean modules that don't have `unsafe` in them.

[2]: https://github.com/dureuill/nolife

[3]: https://arxiv.org/abs/2204.00764


> Interesting, I remember having to adapt some idioms around caching and storing iterators in particular, but very quickly I felt like there wasn't that many hoops and they weren't that weird.

I generally didn't feel that way. I didn't need to change much of my use because O barely ran into the borrow checker. The time I did, was recursively accessing different parts of a pretty central struct but the borrow checker concidered the entire struct a borrowed object. Effectively I couldn't access multiple different fields of the struct if I have the larger struct already as a mutable borrow even though I only access one part of it. It was pretty trivial to confirm that the different functions in fact do not touch the same parts of the struct however the borrow checker simply could not do that. Maybe it's changed recently but I required an actual significant change to the code with several abstractions added when it really wasn't necessary.

I did actually agree with you regarding reviewing code to get rid of UB, I was just a little too verbose maybe. Regarding false positives and negatives with santizers and static analysis, I feel it's worth the pain for a language which I find more expressive for my use case, I'm not against usig rust, I'm against saying it's the universal solution to every problem needing to be solved when in my experience I haven't been able to reproduce that view. There are problems it solves well, I don't run into those often.

I'm also happy more data is coming out on productivity, I feel like it can only help light a fire under people on the standards committee who are indifferent to the issues of C++ to actually push to fix some of the issues which are currently solveable.

Ironically I see with your last point regarding golang that we are very different people ans thats fine. For me I would much rather lean back towards C if I can guarantee safety than the more abstract and high level rust. Honestly I am extremely intrigued by zig but until it's stable I'm not going near it.

We want different things from languages and that is fine.


> Ironically I see with your last point regarding golang that we are very different people ans thats fine. For me I would much rather lean back towards C if I can guarantee safety than the more abstract and high level rust. Honestly I am extremely intrigued by zig but until it's stable I'm not going near it. > > We want different things from languages and that is fine.

I just wanted to tell you that I agree. A lot of what makes people like or dislike a language seems to be down to aesthetics in its nobler meaning.

> The time I did, was recursively accessing different parts of a pretty central struct but the borrow checker concidered the entire struct a borrowed object.

Ah OK. It helps to model a borrow of a struct as a capability. If your struct is made of multiple "capabilities" that can be borrowed separately, then you better express that with a function that borrows the struct and return "view objects" representing the capabilities.

For instance, if you can `foo` and `bar` your struct at the same time, you can have a method:

`fn as_cap(&mut self) -> (Foo<'_>, Bar<'_>) { todo!() }`

and have the `Foo` borrow the fields you need to `foo()` from `self`, and `Bar` borrow the fields you need to `bar()` from `self`.

Then you simply can call `Foo.foo()` and `Bar.bar()`.


> The advantages of a language that is memory safe in practice and also heavily favors correctness in general boosts productivity tenfold in the long term.

Does it though? There are many languages that fit this description, you would choose Rust if for some reason you also need good performance. However, if you heavily interop with C/C++ safety goes out the window anyway, and it probably never mattered much in the first place.


> There are many languages that fit this description

I find that in practice not, especially if you further limit that to imperative languages. Note that I mentioned memory safe AND heavily favors correctness. In that regard, Rust is uniquely placed due to its shared XOR mutable paradigm. One has to look at functional languages that completely disable mutation to find comparable tools for correctness. Allegedly, they're more niche.

> However, if you heavily interop with C/C++ safety goes out the window anyway

I find this to be incorrect. The way you would do this is by (re)writing modules of the application in Rust. Firefox did that for its parallel CSS rendering engine. I did it for the software at my previous job. The software at my current job relies on a C database, we didn't have a memory safety issue in years (never had one since I joined, actually). We have abstracted talking to the DB with a mostly safe wrapper (there are some unsafe functions, but most of them are safe), the very big majority of our code is safe Rust.

> it probably never mattered much in the first place

It does matter. First, for security reasons. Second, because debugging memory issues is not fun and a waste of time when alternatives that fix this class of errors exist.


Memory safety isn't a defining feature of rust in any way and with very very simple rules in C++ (no raw loops and not raw pointer access) you can actually just get rid of memory issues in C++.

There are also pretty easy solutions to indexing issues (reading past the end of a array for instance) which you can use at compile time or just by enabling a compiler flag to have that at least hard crash instead of memory corruption. That takes a lot of RCE vulns to simply DOS vulns which is a significant increase in "security".

Memory safety isn't the reason to use Rust. That's already available in well written C++, and many other languages. Doing it easily with a large group of people I would say is an argument for using rust. But then you need to argue why not swift or java or kotlin or golang a or any of the crop of languages coming out that also offers easy to code memory safety.


> no raw loops and not raw pointer access

- Do these rules allow iterators?

- Under the "no raw pointer" rule, how do you express view objects? For instance, is `std::string_view` forbidden under your rules? If no, then you cannot get rid of memory issues in C++. If yes, then that's a fair bit more than "no raw pointer access", and then how do you take a slice of a string? deep copy? shared_ptr? Both of these solutions are bad for performance, they mean a lot of copies or having all objects reference-counted (which invites atomic increment/decrements overhead, cycles, etc). Compare to the `&str` that Rust affords you.

- What about multithreading? Is that forbidden as well? If it is allowed, what are the simple rules to avoid memory issues such as data races?

> That's already available in well written C++

Where are the projects in well-written C++ that don't have memory-safety CVEs?


You are actually just arguing for the sake of arguing here.

Rust bases all their data structures on pointers just like C++ does, just because you cannot look behind the curtian doesn't mean they aren't there with the same issues. Use the abstractions within the rules and you won't get issues, use compiler flags and analyzers on CI and you don't even need to remember the rules.

And of the billions of lines of code are you really going to try to argue you won't find a single project without a memory safety CVE? You will likely find more than there are rust projects in total, or are we going to shift the goalposts and say they have to be popular, then define popular and prove you won't have a memory safety issue in a similarly sized Rust project. Shift the goalposts again and say "in safe rust" but then why can I not say "in safe C++" and define safe C++ in whatever way I want since the "safe" implementation of rust is defined by the Rust compiler and not a standard or specification and can change from version to version.

I've agreed already that Rust has decent use cases and if you fall into them then and want to use Rust then use Rust. That doesn't mean rust is the only option or even the best one by some measure of best.


> You are actually just arguing for the sake of arguing here.

I'm very much not doing that.

I'm just really tired of reading claims that "C++ is actually safe if you follow these very very simple rules", and then the "simple rules" are either terrible for performance, not actually leading to memory safe code (often by ignoring facts of life like the practices of the standard library, iterator and reference invalidation, or the existence of multithreaded programming), or impossible to reliably follow in an actual codebase. Often all three of these, too.

I mean the most complete version of "just follow rules" is embodied by the C++ core guidelines[1], a 20k lines document of about 116k words, so I think we can drop the "very very simple" qualifier at this point. Many of these rules among the more important are not currently machine-enforceable, like for instance the rules around thread safety.

Meanwhile, the rules for Rust are:

1. Don't use `unsafe`

2. there is no rule #2

*That* is a very very simple rule. If you don't use unsafe, any memory safety issue you would have is not your responsibility, it is the compiler's or your dependency's. It is a radical departure from C++'s "blame the users" stance.

That stance is imposed by the fact that C++ simply doesn't have the tools, at the language level, to provide memory safety. It lacks:

- a borrow checker

- lifetime annotations

- Mutable XOR shared semantics

- the `Send`/`Sync` markers for thread-safety.

Barring the addition of each one of these ingredients, we're not going to see zero-overhead, parallel, memory-safe C++. Adding these is pretty much as big of a change to existing code as switching to Rust, at this point.

> Use the abstractions within the rules and you won't get issues, use compiler flags and analyzers on CI and you don't even need to remember the rules.

I want to see the abstractions, compiler flags and analyzers that will *reliably* find:

- use-after-free issues

- rarely occurring data races in multithreaded code

Use C++ if you want to, but please don't pretend that it is memory safe if following a set of simple rules. That is plain incorrect.

[1]: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines


A significant portion of the core guidelines you don't need to read and are already dealt with in static analysers. As I said, you don't need to know the rules, enable an analyser and you get the exact same experience as rust, your code won't pass the build step.

And am I going to tell the family of the dead person at a funeral my code failed but it's not my fault, the compiler has a bug and I couldn't confirm that myself? And don't pull the "the code is open source you can look at it" crap, I invite you to go and reason about any part of the rust compilers codebase.

In C++ I have multiple compilers all targeting the exact same standard and if in one of them my code is broken I need to explain that, I also have multiple analysers for the same thing.

Also haven't had a single segfault nor memory corruption not index error because I run santizers and analysets on CI, so all those things get caught in build or in testing just like in rust.

Is it as simple as rust, no, and I've already said that but I get the same safety guarantees and I am in control of the code. There isn't some subset of things I cannot do because the languages' built in analysers determines it too expensive to statically analyse that and they don't trust me as a developer to actually write tests and validate all the preconditions I have documented.


These kinds of posts can pretty much always be summerized down to that is what we wanted to use. The arguments are very weak and unconvincing.

People love to say, use the right tool for the job but no one actually does it.

Developers will use what they know or what they feel comfortable using.


I think it's interesting to observe the development speed of Bun vs Deno when comparing Zig and Rust.

Bun is written in Zig and links to the WebKit's JavaScriptCore.

Deno is written in Rust and links to V8.

It seems to me that the development of Bun is significantly faster than Deno, how much of that is down to the easy of interoperability of Zig and C/C++ vs some of the hoops you have to jump though when using Rust with a C/C++ lib?

(I may be wrong on this an the Bun team are just exceptionally quick)


The Bun team is really good, and Zig is a productive language.

But there's also the simple fact that when you reimplement something, it's always going to be easier and faster than it was to write the previous implementation.

You know from day one what the baseline of the project will be, and can skip all the refactoring steps the predecessors had to do in order to implement new features. You also know what worked for them and what didn't. And what users liked and what users didn't use and ended up being code rot, so you can prioritize accordingly.


I thought the creator of Deno, Ryan Dahl, is also the creator of NodeJS. So by your reasoning, the development of Deno should go faster than Bun's, no?


> Zig works with debuggers our developers know out of the box.

Is this not true of Rust? I use lldb via terminal and VSCode with Rust, I am not sure what I'm missing.


It does work from what I can tell. One thing I've not tried is using the expression parser for lldb. I'd be a little bit surprised if that worked for rust and slightly less surprised if it worked for zig.


GDB also works rather well with Rust.


Zig is basically a more ergonomic C while Rust is an entirely different memory-safe language. I don't see why Rust would ever get picked over any other language if high-performance memory safety isn't a priority. And, if it IS a priority, I don't see why anyone would pick any language other than Rust.


I program in Rust and dabbled in Zig. Rust has many more advantages than memory safety, including: * Great error messages (at least since I don't do async stuff) and a helpful linter and compiler * Relatively high-level APIs, especially compared to Zig. I can do much more in fewer lines of code in Rust * Larger ecosystem, including more available packages and tutorials/learning materials * More stability, since Zig is not at 1.0


About Rust's friendly error messages: that's pretty much a self-fulfilled prophecy because compiler errors in Rust are way more prevalent due to stricter constraints imposed by the language. Therefore, Rust must show much better error messages to compensate for its ergonomical disadvantages. So, I don't see that as an absolute win.

All the remaining arguments are about maturity. Nothing prevents Zig from attaining them in the future.

As a final note: I love writing code in Rust and never used Zig, but I can relate to the demand of ergonomics from a programming language and Rust is quite handicapped on that front, IMHO.


There are more error cases for invalid Rust code than for other languages, that might be true, but the friendliness of the errors is a consequence of project phylosophy and engineering effort, not a foregone concussion. Some errors fill in on ergonomic holes, but on the whole they are an attempt to make the DX as good as it can be and making the compiler part of the learning experience, not an inscrutable tool that you need a mentor and experience to even have a chance of understanding.


Given its safety guarantees, I tend to paint Zig as being Modula-2 rebooted for C folks, plus metaprogramming.

Definitely much better than plain old C, still there are caveats.


Priorities aren't necessarily binary. And they might also shift over time.


There are other reasons, like a convenient build system, very flexible generics.


The author redacts so many interesting details about his employer which apparently serves hundreds of millions of requests per day. How peculiar.


I was wondering about that... it is ok to not mention where you work, but here the author highlights the secrecy every two sentences ... I don't understand if it is supposed to be comedic, parodic, or just quirky?

Also, it is interesting that anybody would pick any language because of its C interoperability, since pretty much EVERY production ready programming language can interact with C, although I understand Zig is especially good at this.


Self-described "God’s chosen principal engineer." from their x.com


Plot twist: The company really is called [redacted].


Second plot twist: the one passionate developer who tipped the argument is the author. The article amounts to a public confession.


Third plot twist: not only the choice will echo for decades, it will echo for hundreds of years.


C-interop while certainly more involved in Rust compared to Zig, it is generally a one-off effort (for most projects). wards it's generally an incremental effort to map additional C code. I am not sure I would make this an important factor but to each his own.


> Memory safety and "no undefined behaviors" (obviously untrue, but that was the belief and part of the conversation we had to have

Trying to better understand this claim, is it because also unsafe rust exists?


There are a few corners of undefined behaviors in Rust outside of 'unsafe' blocks, too.


Are you taking about the open unsoundness bugs?


I remember something about being able to transform any lifetime into a static lifetime entirely within safe Rust. But I can't find the link to it right now.


It's either https://github.com/rust-lang/rust/issues/112905, https://github.com/rust-lang/rust/issues/84366 or (most likely) https://github.com/rust-lang/rust/issues/25860.

It can be argued that those are bugs of the compiler, not the language (but in practice that doesn't matter, it's unsound in the only way that matters now!) and that it's very unlikely to be hit by accident (but someone can leverage it intentionally to cause unsoundness in their library, without the presence of unsafe).


Thanks! Yes, I think it was the last one, https://github.com/rust-lang/rust/issues/25860. But I'm not 100% sure.


A question for those who are in the Zig camp.

I've heard talks that zig is now mostly stable and there are no big api changes expected but I haven't been able to find any official statement on that, is this true?


The language is stable enough that we're stopping recommending people to use unstable builds and instead use the latest tagged version. That said every new release has a section about breaking changes and it's never empty.

I would say that calling Zig stable is a huge overstatement, but it is true that the language has been fairly stable in the last few releases. The standard library on the other hand does keep seeing a lot of changes and more in general is still due a big set of breaking changes when it will be officially vetted (it currently grows organically based on needs of the compiler itself or very common use cases).

Lastly, the current stability is in great part caused by the current focus of the core team on custom backends and incremental compilation. Once that's done, I would not be surprised for more language-level breaking changes to pick up again.


Zig language mentioned in the post: https://ziglang.org/


Rust language mentioned in the post: https://www.rust-lang.org/

lol


Had to Google it since the post didn't link to either, but I already knew Rust and assumed many of the readers already do. Also haven't had my morning coffee and thus slight irritated :). Relevant linking should be basic part of any post.


Oh boy, here comes the ZESF

... ;)


TS language mentioned in the post: https://www.typescriptlang.org/


How did you learn Zig if it has no book?


Read the docs, do ziglings for a couple of nights to make it stick a bit better.

Then just start making small things like the "ray tracing in a week" or "Writing an interpreter in Go" book but only the parser part. I usually don't even finish those starter projects its just to get a better feel how to do structs, modules etc.


Not sure about OP, but here's how I did it back when I dabbled in it: had prior programming experience, read the docs. =)


besides the docs, there is also all the source code in /usr/lib/zig/std... very useful to learn zigisms and the API of the std library itself...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: