Thoughts on Go vs. Rust vs. Zig

(sinclairtarget.com)

76 points | by yurivish an hour ago ago

24 comments

  • librasteve 9 minutes ago

    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?

  • publicdebates 15 minutes ago

    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

  • dmoy 32 minutes ago

    For a lot of stuff what I really want is golang but with better generics and result/error/enum handling like rust.

    • Yoric 9 minutes ago

      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 2 minutes ago

        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?

    • evanmoran 23 minutes ago

      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 22 minutes ago

      OCaml is the closest match I'm aware of.

    • throwaway894345 10 minutes ago

      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.

  • ojosilva 14 minutes ago

    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...

  • gaanbal 28 minutes ago

    OP tried zig last and is currently most fascinated by it

  • reeeli 12 minutes ago

    if the languages were creations of LLMs, what would be your (relatively refined) chain(s) of (indulgently) critical thought?

  • throwaway894345 6 minutes ago

    > [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++?

  • skywhopper 29 minutes ago

    Wow, this is a really good writeup without all the usual hangups that folks have about these languages. Well done!

  • 0x457 35 minutes ago

    Reads like a very surface level take with a minor crush on Rob Pike.

  • raggi 22 minutes ago

    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 36 minutes ago

    > 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 29 minutes ago

      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 20 minutes ago

        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 a few seconds ago

          The rope in my example is complexity. Ie choosing to use "all teh features" when you don't need or perhaps even want to.

          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.

    • kace91 25 minutes ago

      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 10 minutes ago

      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 4 minutes ago

      > 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.

    • awesome_dude 25 minutes ago

      > 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)