Thoughts on Go vs. Rust vs. Zig
19 comments
·December 4, 2025publicdebates
Good write up, I like where you're going with this. Your article reads like a recent graduate who's full of excitement and passion for the wonderful world of programming, and just coming into the real world for the first time.
For Go, I wouldn't say that the choice to avoid generics was either intentional or minimalist by nature. From what I recall, they were just struggling for a long time with a difficult decision, which trade-offs to make. And I think they were just hoping that, given enough time, the community could perhaps come up with a new, innovative solution that resolves them gracefully. And I think after a decade they just kind of settled on a solution, as the clock was ticking. I could be wrong.
For Rust, I would strongly disagree on two points. First, lifetimes are in fact what tripped me up the most, and many others, famously including Brian Kernighan, who literally wrote the book on C. Second, Rust isn't novel in combining many other ideas into the language. Lots of languages do that, like C#. But I do recall thinking that Rust had some odd name choices for some features it adopted. And, not being a C++ person myself, it has solutions to many problems I never wrestled with, known by name to C++ devs but foreign to me.
For Zig's manual memory management, you say:
> this is a design choice very much related to the choice to exclude OOP features.
Maybe, but I think it's more based on Andrew's need for Data-Oriented Design when designing high performance applications. He did a very interesting talk on DOD last year[1]. I think his idea is that, if you're going to write the highest performance code possible, while still having an ergonomic language, you need to prioritize a whole different set of features.
dmoy
For a lot of stuff what I really want is golang but with better generics and result/error/enum handling like rust.
Yoric
Have you tried OCaml? With the latest versions, it also has an insanely powerful concurrency model. As far as I understand (I haven't looked at the benchmarks myself), it's also performance-competitive with Go.
evanmoran
I thought the recent error proposal was quite interesting even if it didn't go through: https://github.com/golang/go/issues/71528
My hope is they will see these repeated pain points and find something that fits the error/result/enum issues people have. (Generics will be harder, I think)
throwaway894345
I cautiously agree, with the caveat that while I thought I would really like Rust's error handling, it has been painful in practice. I'm sure I'm holding it wrong, but so far I have tried:
* thiserror: I spend ridiculous and unpredictable amounts of time debugging macro expansions
* manually implementing `Error`, `From`, etc traits: I spend ridiculous though predictable amounts of time implementing traits (maybe LLMs fix this?)
* anyhow: this gets things done, but I'm told not to expose these errors in my public API
Beyond these concerns, I also don't love enums for errors because it means adding any new error type will be a breaking change. I don't love the idea of committing to that, but maybe I'm overthinking?
And when I ask these questions to various Rust people, I often get conflicting answers and no one seems to be able to speak with the authority of canon on the subject. Maybe some of these questions have been answered in the Rust Book since I last read it?
By contrast, I just wrap Go errors with `fmt.Errorf("opening file `%s`: %w", filePath, err)` and handle any special error cases with `errors.As()` and similar and move on with life. It maybe doesn't feel _elegant_, but it lets me get stuff done.
mixedCase
OCaml is the closest match I'm aware of.
gaanbal
OP tried zig last and is currently most fascinated by it
reeeli
if the languages were creations of LLMs, what would be your (relatively refined) chain(s) of (indulgently) critical thought?
ojosilva
Fine, but there's a noticeable asymmetry in how the three languages get treated. Go gets dinged for hiding memory details from you. Rust gets dinged for making mutable globals hard and for conceptual density (with a maximally intimidating Pin quote to drive it home). But when Zig has the equivalent warts they're reframed as virtues or glossed over.
Mutable globals are easy in Zig (presented as freedom, not as "you can now write data races.")
Runtime checks you disable in release builds are "highly pragmatic," with no mention of what happens when illegal behavior only manifests in production.
The standard library having "almost zero documentation" is mentioned but not weighted as a cost the way Go's boilerplate or Rust's learning curve are.
The RAII critique is interesting but also somewhat unfair because Rust has arena allocators too, and nothing forces fine-grained allocation. The difference is that Rust makes the safe path easy and the unsafe path explicit whereas Zig trusts you to know what you're doing. That's a legitimate design, hacking-a!
The article frames Rust's guardrails as bureaucratic overhead while framing Zig's lack of them as liberation, which is grading on a curve. If we're cataloging trade-offs honestly
> you control the universe and nobody can tell you what to do
...that cuts both ways...
skywhopper
Wow, this is a really good writeup without all the usual hangups that folks have about these languages. Well done!
0x457
Reads like a very surface level take with a minor crush on Rob Pike.
raggi
I really hate the anti-RAII sentiments and arguments. I remember the Zig community lead going off about RAII before and making claims like "linux would never do this" (https://github.com/torvalds/linux/blob/master/include/linux/...).
There are bad cases of RAII APIs for sure, but it's not all bad. Andrew posted himself a while back about feeling bad for go devs who never get to debug by seeing 0xaa memory segments, and sure I get it, but you can't make over-extended claims about non-initialization when you're implicitly initializing with the magic value, that's a bit of a false equivalence - and sure, maybe you don't always want a zero scrub instead, I'm not sold on Go's mantra of making zero values always be useful, I've seen really bad code come as a result of people doing backflips to try to make that true - a constructor API is a better pattern as soon as there's a challenge, the "rule" only fits when it's easy, don't force it.
Back to RAII though, or what people think of when they hear RAII. Scope based or automatic cleanup is good. I hate working with Go's mutex's in complex programs after spending life in the better world. People make mistakes and people get clever and the outcome is almost always bad in the long run - bugs that "should never get written/shipped" do come up, and it's awful. I think Zig's errdefer is a cool extension on the defer pattern, but defer patterns are strictly worse than scope based automation for key tasks. I do buy an argument that sometimes you want to deviate from scope based controls, and primitives offering both is reasonable, but the default case for a ton of code should be optimized for avoiding human effort and human error.
In the end I feel similarly about allocation. I appreciate Zig trying to push for a different world, and that's an extremely valuable experiment to be doing. I've fought allocation in Go programs (and Java, etc), and had fights with C++ that was "accidentally" churning too much (classic hashmap string spam, hi ninja, hi GN), but I don't feel like the right trade-off anywhere is "always do all the legwork" vs. "never do all the legwork". I wish Rust was closer to the optimal path, and it's decently ergonomic a lot of the time, but when you really want control I sometimes want something more like Zig. When I spend too much time in Zig I get a bit bored of the ceremony too.
I feel like the next innovation we need is some sanity around the real useful value that is global and thread state. Far too much toxic hot air is spilled over these, and there are bad outcomes from mis/overuse, but innovation could spend far more time on _sanely implicit context_ that reduces programmer effort without being excessively hidden, and allowing for local specialization that is easy and obvious. I imagine it looks somewhere between the rust and zig solutions, but I don't know exactly where it should land. It's a horrible set of layer violations that the purists don't like, because we base a lot of ABI decisions on history, but I'd still like to see more work here.
So RAII isn't the big evil monster, and we need to stop talking about RAII, globals, etc, in these ways. We need to evaluate what's good, what's bad, and try out new arrangements maximize good and minimize bad.
echelon
> Many people seem confused about why Zig should exist if Rust does already. It’s not just that Zig is trying to be simpler. I think this difference is the more important one. Zig wants you to excise even more object-oriented thinking from your code.
I feel like Zig is for the C / C++ developers that really dislike Rust.
There have been other efforts like Carbon, but this is the first that really modernizes the language and scratches new itches.
> I’m not the first person to pick on this particular Github comment, but it perfectly illustrates the conceptual density of Rust: [crazy example elided]
That is totally unfair. 99% of your time with Rust won't be anything like that.
> This makes Rust hard, because you can’t just do the thing! You have to find out Rust’s name for the thing—find the trait or whatever you need—then implement it as Rust expects you to.
What?
Rust is not hard. Rust has a standard library that looks an awful lot like Python or Ruby, with similarly named methods.
If you're trying to shoehorn some novel type of yours into a particular trait interface so you can pass trait objects around, sure. Maybe you are going to have to memorize a lot more. But I'd ask why you write code like that unless you're writing a library.
This desire of wanting to write OO-style code makes me think that people who want OO-style code are the ones having a lot of struggle or frustration with Rust's ergonomics.
Rust gives you everything OO you'd want, but it's definitely more favorable if you're using it in a functional manner.
> makes consuming libraries easy in Rust and explains why Rust projects have almost as many dependencies as projects in the JavaScript ecosystem.
This is one of Rust's superpowers !
the__alchemist
Same. Zig's niche is in the vein of languages that encourages using pointers for business logic. If you like this style, Rust and most other new languages aren't an option.
unshavedyak
Rust is hard in that it gives you a ton of rope to hang yourself with, and some people are just hell bent on hanging themselves.
I find Rust quite easy most of the time. I enjoy the hell out of it and generally write Rust not too different than i'd have written my Go programs (i use less channels in Rust though). But i do think my comment about rope is true. Some people just can't seem to help themselves.
nicoburns
That seems like an odd characterization of Rust. The borrow checker and all the other type safety features, as well as features like send/sync are all about not giving you rope to hang yourself with.
kace91
One question about your functional point: where can I learn functional programming in terms of organization of large codebases?
Perhaps it is because DDD books and the like usually have strong object oriented biases, but whenever I read about functional programming patterns I’m never clear on how to go from exercise stuff to something that can work in a real world monolith for example.
And to be clear I’m not saying functional programming is worse at that, simply that I have not been able to find information on the subject as easily.
awesome_dude
> Rust is not hard. Rust has a standard library that looks an awful lot like Python or Ruby, with similarly named methods.
> If you're trying to shoehorn some novel type of yours into a particular trait interface so you can pass trait objects around, sure. Maybe you are going to have to memorize a lot more. But I'd ask why you write code like that unless you're writing a library.
I think that you are missing the point - they're not saying (at least in my head) "Rust is hard because of all the abstractions" but, more "Rust is hard because you are having to explain to the COMPILER [more explicitly] what you mean (via all these abstractions)
And I think that that's a valid assessment (hell, most Rustaceans will point to this as a feature, not a bug)
I love this take - partly because I agree with it - but mostly because I think that this is the right way to compare PLs (and to present the results). It is honest in the way it ascribes strengths and weaknesses, helping to guide, refine, justify the choice of language outside of job pressures.
I am sad that it does not mention Raku (https://raku.org) ... because in my mind there is a kind of continuum: C - Zig - C++ - Rust - Go ... OK for low level, but what about the scriptier end - Julia - R - Python - Lua - JavaScript - PHP - Raku - WL?