Live data from Hacker News

Thoughts on Go vs. Rust vs. Zig

sinclairtarget.com

461–470 of 599 posts

Re: Thoughts on Go vs. Rust vs. Zig

#461
post #214

Earlier quoted context omitted.

> [...] is trivial in Rust [...] it just requires [...] This is a tombstone-quality statement. It's the same framing people tossed around about C++ and Perl and Haskell (also Prolog back in the day). And it's true, insofar as it goes. But languages where "trivial" things "just require" rapidly become "not so trivial" in the aggregate. And Rust has jumped that particular shark. It will never be trivial, period.

Well-designed programming languages should disincentivize from following a wrong practice and Rust is following the right course here.

The caveat here is that there is a complexity cost in borrowing mechanics and for a large number of applications it might not be the best option.

Re: Thoughts on Go vs. Rust vs. Zig

#462

I've been using Zig for few days. And my gotchas so far: - Can't `for (-1..1) {`. Must use `while` instead. - if you allocated something inside of a block and you want it to keep existing outside of a block `defer` won't help you to deallocate it. I didn't find a way to defer something till the end of the function. - adding variable containing -1 to usize variable is cumbersome. You are better of running everything w…

I don't know if that's just aged noob in me speaking but so far, while Rust has "zero cost abstractions", Zig feels like it has "Zero abstractions".

Deallocating the wrong thing or the right thing too soon bit me in th ass so much already that I feel craving for destructors.

Re: Thoughts on Go vs. Rust vs. Zig

#463
post #245

Earlier quoted context omitted.

Self-aware people are mindful about what "future them" might do in various scenarios, and they plan ahead to tamp down their worse tendencies. I don't keep a raspberry cheesecake in my fridge, even though that would maximize a certain kind of freedom (the ability to eat cheesecake whenever I want). I much prefer the freedom that comes with not being tempted, as it leads to better outcomes on things I really care abou…

I would rather live in a world where I can put a raspberry cheesecake in my fridge occasionally. Because I know how to enjoy cheesecake without having to buy it every week. Not a world where when I pick the cheesecake off the shelf in the store someone says "Raspberry cheesecake! You may be one of these people who is lacking in self awareness so let me guide you. Did you know that it might be unsafe! Are you sure it'…

I think I see it slightly differently. Culture is complex: I would not generally use the word “force” to describe it; I would say culture influences and shapes. When I think of force I think of coercion such as law and punishment.

When looking at various programming languages, we see a combination of constraints, tradeoffs, surrounding cultures, and nudges.

For example in Rust, the unsafe capabilities are culturally discouraged unless needed. Syntax-wise it requires extra ceremony.

Re: Thoughts on Go vs. Rust vs. Zig

#464

Earlier quoted context omitted.

Interesting. It is semi-rare that I meet someone who knows both Rust and Go and prefers Go. Is it the velocity you get from coding in it? I have a love/hate relationship with Go. I like that it lets me code ideas very fast, but my resulting product just feels brittle. In Rust I feel like my code is rock solid (with the exception of logic, which needs as much testing as any other lang) often without even testing, just…

I think this is kind of a telling observation, because the advantage to working in Go over Rust is not subtle: Go has full automatic memory management and Rust doesn't. Rust is safe , like Go is, but Rust isn't as automatic. Building anything in Rust requires me to make a series of decisions that Go doesn't ask me to make. Sometimes being able to make those decisions is useful, but usually it is not. The joke I like…

> Go has full automatic memory management and Rust doesn't

It doesn't? In Go, I allocate (new/make or implicit), never free. In Rust, I allocate (Box/Arc/Rc/String), never free. I'm not sure I see the difference (other than allocation is always more explicit in Rust, but I don't see that as a downside). Or are you just talking about how Go is 100% implicit on stack vs heap allocation?

> Sometimes being able to make those decisions is useful, but usually it is not.

Rust makes you think about ownership. I generally like the "feeling" this gives me, but I will agree it is often not necessary and "just works" in GC langs.

> I actually like computer science, and I like to be able to lay out a tree structure when it makes sense to do so, without consulting a very large book premised on how hard it is to write a doubly-linked list in Rust. The fun thing is landing that snark and seeing people respond "well, you shouldn't be freelancing your own mutable tree structures, it should be hard to work with trees", from people who apparently have no conception of a tree walk other than as a keyed lookup table implementation.

I LOVE computer science. I do trees quite often, and they aren't difficult to do in Rust, even doubly linked, but you just have to use indirection. I don't get why everyone thinks they need to do them with pointers, you don't.

    enum Node {
        Leaf,
        Branch {child: Rc, parent: Option> },
    }
Compared to something like Java/C# or anything with a bump allocator this would actually be slower, as Rust uses malloc/free, but Go suffers from the same achilles heel here (see any tree benchmark). In Rust, I might reach for Bumpalo to build the tree in a single allocation (an arena crate), but only if I needed that last ounce of speed.

If you need to edit your tree, you would also want the nodes wrapped in a `RefCell`.

Re: Thoughts on Go vs. Rust vs. Zig

#465
post #353
post #287

Earlier quoted context omitted.

Even on Linux with overcommit you can have allocations fail, in practical scenarios. You can impose limits per process/cgroup. In server environments it doesn't make sense to run off swap (the perf hit can be so large that everything times out and it's indistinguishable from being offline), so you can set limits proportional to physical RAM, and see processes OOM before the whole system needs to resort to OOMKiller.…

I hear this claim on swap all the time, and honestly it doesn't sound convincing. Maybe ten or twenty years ago, but today? CAS latency for DIMM has been going UP, and so is NVMe bandwidth. Depending on memory access patterns, and whether it fits in the NVMe controller's cache (the recent Samsung 9100 model includes 4 GB of DDR4 for cache and prefetch) your application may work just fine.

Swap can be fine on desktops where usage patterns vary a lot, and there are a bunch of idle apps to swap out. It might be fine on a server with light loads or a memory leak that just gets written out somewhere.

What I had in mind was servers scaled to run near maximum capacity of the hardware. When the load exceeds what the server can handle in RAM and starts shoving requests' working memory into swap, you typically won't get higher throughput to catch up with the overload. Swap, even if "fast enough", will slow down your overall throughput when you need it to go faster. This will make requests pile up even more, making more of them go into swap. Even if it doesn't cause a death spiral, it's not an economical way to run servers.

What you really need to do is shed the load before it overwhelms the server, so that each box runs at its maximum throughput, and extra traffic is load-balanced elsewhere, or rejected, or at least queued in some more deliberate and efficient fashion, rather than franticly moving server's working memory back and forth from disk.

You can do this scaling without OOM handling if you have other ways of ensuring limited memory usage or leaving enough headroom for spikes, but OOM handling lets you fly closer to the sun, especially when the RAM cost of requests can be very uneven.

Re: Thoughts on Go vs. Rust vs. Zig

#466
post #28

Earlier quoted context omitted.

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…

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, whic…

> In Go, nobody seems to care about that, which of course makes code easier to write, but catching errors quickly becomes stringly typed.

In Go we just use errors.Is() or errors.As() to check for specific error values or types (respectively). It’s not stringly typed.

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

That makes sense. I think the main grievance with Rust’s error handling is that, while I’m sure there is the possibility to use anyhow, thiserror, non_exhaustive, etc in various combinations to build an overall elegant error handling system, that system isn’t (last I checked) canon, and different people give different, sometimes contradictory advice.

Re: Thoughts on Go vs. Rust vs. Zig

#467
post #222

Earlier quoted context omitted.

> languages where "trivial" things "just require" rapidly become "not so trivial" in the aggregate Sure. And in C and Zig, it's "trivial" to make a global mutable variable, it "just requires" you to flawlessly uphold memory access invariants manually across all possible concurrent states of your program. Stop beating around the bush. Rust is just easier than nearly any other language for writing concurrent programs,…

This is a miscommunication between the values of “shipping” which optimizes for fastest time to delivery and “correctness” which optimizes for the quality of the code. Rust makes it easy to write correct software quickly, but it’s slower for writing incorrect software that still works for an MVP. You can get away with writing incorrect concurrent programs in other languages… for a while. And sometimes that’s what bus…

There is a real argument to be made that quick prototyping in Rust is unintuitive compared to other languages, however it's definitely possible and does not even impact iteration speed all that much: the only cost is some extra boilerplate, without even needing to get into `unsafe` code. You don't get the out-of-the-box general tracing GC that you have in languages like Golang, Java/C# or ECMAScript, or the bignum-by-default arithmetic of Python, but pretty much every other basic facility is there, including dynamic variables (the `Any` trait).

Re: Thoughts on Go vs. Rust vs. Zig

#468
post #289

Earlier quoted context omitted.

That isn't apples to apples. In Rust I could have done (assuming `anyhow::Error` or `Box ` return types, which are very typical): let mut file = File::create("foo.txt") .map_err(|e| format!("failed to create file: {e}")?; Rust having the subtle benefit here of guaranteeing at compile type that the parameter to the string is not omitted. In Go I could have done (and is just as typical to do): f, err := os.Create("file…

You could have done that in Rust but you wouldn't, because the allure of just typing a single character of ? is too strong. The UX is terrible — the path of least resistance is that of laziness. You should be forced to provide an error message, i.e. ?("failed to create file: {e}") should be the only valid form. In Go, for one reason or another, it's standard to provide error context; it's not typical at all to just r…

> You could have done that in Rust but you wouldn't, because the allure of just typing a single character of ? is too strong.

You could have done that in Go but you wouldn't, because the allure of just typing two words

    return err
is too strong.

Quite literally the same thing and the only difference is bias and habit.

Re: Thoughts on Go vs. Rust vs. Zig

#469
post #412

Earlier quoted context omitted.

I agree, I think they should have delayed it. In a different universe rust still does not have async and in 5 years it might get an ocaml-style effect system.

And in that universe Rust is likely an inconsequential niche language.

If rust skipped async features I think it would not have damaged it much

Re: Thoughts on Go vs. Rust vs. Zig

#470

Earlier quoted context omitted.

> but I keep running up against the "immutable variable" problem ...Is that not what mut is for? I'm a bit confused what you're talking about here.

I don't really get immutable variables, or why you'd want to make copies of things so now you've got an updated variable and an out-of-date variable. Isn't that just asking for bugs?

As with many things, it comes down to tradeoffs. Immutable variables have one set of characteristics/benefits/drawbacks, and mutable variables have another. Different people will prefer one over the other, different scenarios will favor one over the other, and that's expected.

That being said, off the top of my head I think immutability is typically seen to have two primary benefits:

- No "spooky action at a distance" is probably the biggest draw. Immutability means no surprises due to something else you didn't expect mutating something out from under you. This is particularly relevant in larger codebases/teams and when sharing stuff in concurrent/parallel code.

- Potential performance benefits. Immutable objects can be shared freely. Safe subviews are cheap to make. You can skip making defensive copies. There are some interesting data structures which rely on their elements being immutable (e.g., persistent data structures). Lazy evaluation is more feasible. So on and so forth.

Rust is far from the first language to encourage immutability to the extent it does - making immutable objects has been a recommendation in Java for over two decades at this point, for example, to say nothing of its use of immutable strings from the start, and functional programming languages have been working with it even longer. Rust also has one nice thing as well which helps address this concern:

> or why you'd want to make copies of things so now you've got an updated variable and an out-of-date variable

The best way to avoid this in Rust (and other languages with similarly capable type systems) is to take advantage of how Rust's move semantics work to make the old value inaccessible after it's consumed. This completely eliminates the possibility that the old values anre accidentally used. Lints that catch unused values provide additional guardrails.

Obviously this isn't a universally applicable technique, but it's a nice tool in the toolbox.

In the end, though, it's a tradeoff, as I said. It's still possible to accidentally use old values, but the Rust devs (and the community in general, I think) seem to have concluded that the benefits outweigh the drawbacks, especially since immutability is just a default rather than a hard rule.

Post reply on HN