[HN Gopher] Thoughts on Go vs. Rust vs. Zig
___________________________________________________________________
Thoughts on Go vs. Rust vs. Zig
Author : yurivish
Score : 106 points
Date : 2025-12-04 21:40 UTC (1 hours ago)
(HTM) web link (sinclairtarget.com)
(TXT) w3m dump (sinclairtarget.com)
| echelon wrote:
| > 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 !
| unshavedyak wrote:
| 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 wrote:
| 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.
| unshavedyak wrote:
| The rope in my example is complexity. Ie choosing to use
| "all teh features" when you don't need or perhaps even want
| to. Eg sometimes a simple clone is fine. Sometimes you
| don't need to opt for every generic and performance minded
| feature Rust offers - which are numerous.
|
| Though, i think my statement is missing something. I moved
| from Go to Rust because i found that Rust gave me better
| tooling to encapsulate and reuse logic. Eg Iterators are
| more complex under the hood, but my observed complexity was
| lower in Rust compared to Go by way of better, more
| generalized code reuse. So in this example i actually found
| Go to be more complex.
|
| So maybe a more elaborated phrase would be something like
| Rust gives you more visible rope to hang yourself with..
| but that doesn't sound as nice. I still like my original
| phrase heh.
| awesome_dude wrote:
| > 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)
| kace91 wrote:
| 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.
| the__alchemist wrote:
| 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.
| tiltowait wrote:
| > Rust has a standard library that looks an awful lot like
| Python or Ruby, with similarly named methods.
|
| Can you elaborate? While they obviously have overlap, Rust's
| stdlib is deliberately minimal (you don't even get RNG without
| hitting crates.io), whereas Python's is gigantic. And in actual
| use, they tend to feel extremely different.
| Yoric wrote:
| > I feel like Zig is for the C / C++ developers that really
| dislike Rust.
|
| Also my feeling. Writing this as a former C++ developer who
| really likes Rust :)
| Quothling wrote:
| > Rust is not hard. Rust has a standard library that looks an
| awful lot like Python or Ruby, with similarly named methods.
|
| I would read this in regard to Go and not so much in regards to
| Zig. Go is insanely productive, and while you're not going to
| match something like Django in terms of delivery speed with
| anything in Go, you almost can... and you can do it without
| using a single external dependency. Go loses a little of this
| in the embeded space, where it's not quite as simple, but the
| opinonated approach is still very productive even here.
|
| I can't think of any language where I can produce something as
| quickly as I can in Go with the use of nothing but the standard
| library. Even when you do reach for a framework like SQLC, you
| can run the external parts in total isolation if that's your
| thing.
|
| I will say that working with the interoperability of Zig in our
| C for Python binaries has been very easy, which it wasn't for
| Rust. This doesn't mean it's actually easier for other people,
| but it sure was for me.
|
| > This is one of Rust's superpowers !
|
| In some industries it's really not.
| 0x457 wrote:
| Reads like a very surface level take with a minor crush on Rob
| Pike.
| dmoy wrote:
| For a lot of stuff what I really want is golang but with better
| generics and result/error/enum handling like rust.
| evanmoran wrote:
| 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)
| mixedCase wrote:
| OCaml is the closest match I'm aware of.
| throwaway894345 wrote:
| 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.
| thijsr wrote:
| > I also don't love enums for errors because it means adding
| any new error type will be a breaking change
|
| You can annotate your error enum with #[non_exhaustive], then
| it will not be a breaking change if you add a new variant.
| Effectively, you enforce that anybody doing a match on the
| enum must implement the "default" case, i.e. that nothing
| matches.
| Yoric wrote:
| FWIW `fmt.Errorf("opening file %s: %w", filePath, err)` is
| pretty much equivalent to calling `err.with_context(||
| format!("opening file {}", path))?` with anyhow.
|
| What `thiserror` or manually implementing `Error` buys you is
| the ability to actually do something about higher-level
| errors. In Rust design, not doing so in a public facing API
| is indeed considered bad practice. In Go, nobody seems to
| care about that, which of course makes code easier to write,
| but catching errors quickly becomes stringly typed. Yes, it's
| possible to do it correctly in Go, but it's ridiculously
| complicated, and I don't think I've ever seen any third-party
| library do it correctly.
|
| That being said, I agree that manually implementing `Error`
| in Rust is way too time-consuming. There's also the added
| complexity of having to use a third-party crate to do what
| feels like basic functionality of error-handling. I haven't
| encountered problems with `thiserror` yet.
|
| > 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?
|
| If you wish to make sure it's not a breaking change, mark
| your enum as `#[non_exhaustive]`. Not terribly elegant, but
| that's exactly what this is for.
|
| Hope it helped a bit :)
| iterance wrote:
| I will at least remark that adding a new error to an enum is
| not a breaking change if they are marked #[non_exhaustive].
| The compiler then guarantees that all match statements on the
| enum contain a generic case.
|
| However, I wouldn't recommend it. Breakage over errors is not
| necessarily a bad thing. If you need to change the API for
| your errors, and downstreams are required to have generic
| cases, they will be forced to silently accept new error types
| without at least checking what those new error types are for.
| This is disadvantageous in a number of significant cases.
| written-beyond wrote:
| You have to chill with rust. Just anyhow macro wrap your
| errors and just log them out. If you have a specific use case
| that relies on using that specific error just use that at the
| parent stack.
| snuxoll wrote:
| > 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?
|
| Is it a new error condition that downstream consumers want to
| know about so they can have different logic? Add the enum
| variant. The entire point of this pattern is to do what typed
| exceptions in Java were supposed to do, give consuming code
| the ability to reason about what errors to expect, and handle
| them appropriately if possible.
|
| If your consumer can't be reasonably expected to recover? Use
| a generic failure variant, bonus points if you stuff the
| inner error in and implement std::Error so consumers can get
| the underlying error by calling .source() for debugging at
| least.
|
| > 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.
|
| Nothing stopping you from doing the same in Rust, just add a
| match arm with a wildcard pattern (_) to handle everything
| but your special cases.
|
| In fact, if you suspect you are likely to add additional
| error variants, the `#[non_exhaustive]` attribute exists
| explicitly to handle this. It will force consumers to provide
| a match arm with a wildcard pattern to prevent additions to
| the enum from causing API incompatibility. This does come
| with some other limitations, so RTFM on those, but it does
| allow you to add new variants to an Error enum without
| requiring a major semver bump.
| Yoric wrote:
| 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.
| throwaway894345 wrote:
| How's the build tooling these days? Last I tried, it used
| some jbuild/dune + makefiles thing that was really painful to
| get up and running. Also there were multiple standard
| libraries and (IIRC) async runtimes that wouldn't play nicely
| together. The syntax and custom operators was also a thing
| that I could not stop stubbing my toes on--while I previously
| thought syntax was a relatively unimportant concern, my
| experience with OCaml changed my mind. :)
|
| Also, at least at the time, the community was really hostile,
| but that was true of C++, Ada, and Java communities as well
| well. But I think those guys have chilled out, so maybe OCaml
| has too?
| Yoric wrote:
| I'm re-discovering OCaml these days after an OCaml burnout
| quite a few years ago, courtesy of my then employer, so I'm
| afraid I can't answer these questions reliably :/
|
| So far, I like what I've seen.
| myaccountonhn wrote:
| Ocaml community is chill and helpful, and dune works great
| with really good compilation speeds.
|
| Its a really nice language
| zozbot234 wrote:
| There's also ReasonML if you want an OCaml with curly braces
| like C. But both are notably missing the high-performance
| concurrent GC that ships with Golang out of the box.
| Philpax wrote:
| You want https://github.com/borgo-lang/borgo, but that project
| is dead. You might be interested in Gleam?
| skywhopper wrote:
| Wow, this is a really good writeup without all the usual hangups
| that folks have about these languages. Well done!
| gaanbal wrote:
| OP tried zig last and is currently most fascinated by it
| raggi wrote:
| 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/torva
| lds/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.
| publicdebates wrote:
| 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.
|
| [1] https://www.youtube.com/watch?v=IroPQ150F6c
| ojosilva wrote:
| 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...
| ekropotin wrote:
| I pretty new to Rust and I'm wondering why global mutables are
| hard?
|
| At first glance you can just use static variable of a type
| supporting interior mutability - RefCell, Mutex, etc...
| written-beyond wrote:
| I don't think it's specifically hard, it's more related to
| how it probably needed more plumbing in the language that
| authors thought would add to much baggage and let the
| community solve it. Like the whole async runtime debates
| reeeli wrote:
| if the languages were creations of LLMs, what would be _your_
| (relatively refined) chain(s) of (indulgently) critical thought?
| librasteve wrote:
| 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?
| librasteve wrote:
| I tried to get an LLM to write a Raku chapter in the same vein
| - naah. Had to write it myself:
|
| Raku
|
| Raku stands out as a fast way to working code, with a
| permissive compiler that allows wide expression.
|
| Its an expressive, general-purpose language with a wide set of
| built-in tools. Features like multi-dispatch, roles, gradual
| typing, lazy evaluation, and a strong regex and grammar system
| are part of its core design. The language aims to give you
| direct ways to reflect the structure of a problem instead of
| building abstractions from scratch.
|
| The grammar system is the clearest example. Many languages
| treat parsing as a specialized task requiring external
| libraries. Raku instead provides a declarative syntax for
| defining rules and grammars, so working with text formats,
| logs, or DSLs often requires less code and fewer workarounds.
| This capability blends naturally with the rest of the language
| rather than feeling like a separate domain.
|
| Raku programs run on a sizeable VM and lean on runtime
| dispatch, which means they typically don't have the startup
| speed or predictable performance profile of lower-level or more
| static languages. But the model is consistent: you get
| flexibility, clear semantics, and room to adjust your approach
| as a problem evolves. Incremental development tends to feel
| natural, whether you're sketching an idea or tightening up a
| script that's grown into something larger.
|
| The language's long development history stems from an attempt
| to rethink Perl, not simply modernize it. That history produced
| a language that tries to be coherent and pleasant to write,
| even if it's not small. Choose Raku if you want a language that
| let's you code the way you want, helps you wrestle with the
| problem and not with the compiler.
| throwaway894345 wrote:
| > [Go] is like C in that you can fit the whole language in your
| head.
|
| Go isn't like C in that you can _actually_ fit the entire
| language in your head. Most of us who think we have fit C in our
| head will still stumble on endless cases where we didn 't realize
| X was actually UB or whatever. I wonder how much C's reputation
| for simplicity is an artifact of its long proximity to C++?
| kachapopopow wrote:
| I could never get into zig purely because of the syntax and I
| know I am not alone, can someone explain the odd choices that
| were taken when creating zig?
|
| the most odd one probably being 'const expected = [_]u32{ 123,
| 67, 89, 99 };'
|
| and the 2nd most being the word 'try' instead of just ?
|
| the 3rd one would be the imports
|
| and `try std.fs.File.stdout().writeAll("hello world!\n");` is not
| really convincing either for a basic print.
| von_lohengramm wrote:
| > and the 2nd most being the word 'try' instead of just ?
|
| All control flow in Zig is done via keyword
| dwb wrote:
| These are extremely trivial, to the point that I don't really
| know what you're complaining about. What would expect or
| prefer?
| vlovich123 wrote:
| Re UB:
|
| > The idea seems to be that you can run your program enough times
| in the checked release modes to have reasonable confidence that
| there will be no illegal behavior in the unchecked build of your
| program. That seems like a highly pragmatic design to me.
|
| This is only pragmatic if you ignore the real world experience of
| sanitizers which attempt to do the same thing and failing to
| prevent memory safety and UB issues in deployed C/C++ codebases
| (eg Android definitely has sanitizers running on every commit and
| yet it wasn't until they switched to Rust that exploits started
| disappearing).
| kibwen wrote:
| _> In Rust, creating a mutable global variable is so hard that
| there are long forum discussions on how to do it. In Zig, you can
| just create one, no problem._
|
| Well, no, creating a mutable global variable is trivial in Rust,
| it just requires either `unsafe` or using a smart pointer that
| provides synchronization. That's because Rust programs are re-
| entrant by default, because Rust provides compile-time thread-
| safety. If you don't care about statically-enforced thread-
| safety, then it's as easy in Rust as it is in Zig or C. The
| difference is that, unlike Zig or C, Rust gives you the tools to
| enforce more guarantees about your code's possible runtime
| behavior.
| Aperocky wrote:
| Anecdotally, as a result of the traits that made it hard to learn
| for humans, Rust is actually a great language for LLM.
|
| Out of all languages I do development in the past few months: Go,
| Rust, Python, Typescript; Rust is the one that LLM has the least
| churn/problems in terms of producing correct and functional code
| given a problem of similar complexity.
|
| I think this outside factor will eventually win more usage for
| Rust.
___________________________________________________________________
(page generated 2025-12-04 23:00 UTC)