Live data from Hacker News

Thoughts on Go vs. Rust vs. Zig

sinclairtarget.com

531–540 of 599 posts

Re: Thoughts on Go vs. Rust vs. Zig

#531
post #62

Earlier quoted context omitted.

You can just as easily add context to the first example or skip the wrapping in the second.

Especially since the second example only gives you a stringly-typed error. If you want to add 'proper' error types, wrapping them is just as difficult in Go and Rust (needing to implement `Error` in Go or `std::Error` in Rust). And, while we can argue about macro magic all day, the `thiserror` crate makes said boilerplate a non-issue and allows you to properly propagate strongly-typed errors with context when needed…

fmt.Errorf with %w directive in fact wraps an error. It will return an fmt.wrapError struct which can be inspected using `errors.Is`. So it's not stringly typed anymore.

Re: Thoughts on Go vs. Rust vs. Zig

#532

Earlier quoted context omitted.

I'm in a similar place, but my stack is Python->Go With Python I can easily iterate on solutions, observe them as they change, use the REPL to debug things and in general just write bad code just to get it working. I do try to add type annotations etc and not go full "yolo Javascript everything is an object" -style :) But in the end running Python code on someone else's computer is a pain in the ass, so when I'm done…

I'm in the camp of "If your target is Go, then prototype in Go." I don't bother with the intermediate step. Go is already so very close to being a dynamic language that I don't get the point. Just write "bad" Go to prototype quickly. Skip the error checks. Panic for fun. Write long functions. Make giant structs. Don't worry about memory. You mentioned running someone else's python is painful, and it most certainly is…

I second your experience with Python. I've been coding in Python for 10+ years. When I get passed down some 'data scientist' code, I often it breaks.

With Rust, it was amazing - it was a pain to get it compiled and get past the restrictions (coming from a Python coder) - the code just ran without a hitch, and it was fast, never even tried to optimize it.

As a Python 'old-timer' , I also am not impressed with all the gratuitous fake typing , and especially Pydantic. Pydantic feels so un-pythonic, they're trying to make it like Go or Rust, but its falling flat, at least for me.

Re: Thoughts on Go vs. Rust vs. Zig

#534

Earlier quoted context omitted.

So, one year ago? After more than 25 years without it? And a lot of people writing Java can't update to that.

Yeah, FFM adds many quality-of-life features - scoped lifetimes being a standout. If you just want an arena interface, ByteBuffer has been there since Java 1.4 (2002). It also does off-heap w/ ByteBuffer.allocateDirect(). https://docs.oracle.com/en/java/javase/25/docs/api/java.base...

That brings back some memories...when Frank Yellin and Sheng Liang (from Javasoft) visited our team at UW I think in 1996 or 1997, we mentioned that our bytecode verifier/failure injector was really fast because we were using arena allocation, which was only possible (at the time) because we did the project in C. If you are using non-pointer offsets, you totally can do arena allocation with a byte buffer. I still don't get how you can do it safely with high level objects even with the mechanism described above, but there are definitely lots of unsafe ways to do it.

Re: Thoughts on Go vs. Rust vs. Zig

#535
post #491
post #451

Earlier quoted context omitted.

I disagree, adding context to errors provide exactly what is needed to debug the issue. If you don't have enough context it's your fault, and context will contain more useful info than a stack trace (like the user id which triggered the issue, or whatever is needed). Stack traces are reserved for crashes where you didn't handle the issue properly, so you get technical info of what broke and where, but no info on what…

It's one piece of information, but logging at the error location does that still. And if you have a function that's called in multiple places how do you know the path that got you into that place. If it wasn't useful we wouldn't try to recreate them with wrapped errors

You wrap errors primarily to avoid the implementation detail leak. Even where errors have stack traces, you still need to do that, as was already described earlier. What debugging advantage comes with that is merely an aded bonus (A really nice bonus, to be sure. Attaching stack traces is computationally wasteful, so it is a win to not have to include them).

You can get away with not doing that when cowboy coding scripts. Python was designed to be a scripting language, so it is understandable that in Python you don't often need to worry about it. But Go isn't a scripting language. It was quite explicitly created to be a systems language. Scripts and systems are very different types of software with very different needs and requirements. If you are stuck thinking in terms of what is appropriate for scripting, you're straight up not participating in the same thread.

Re: Thoughts on Go vs. Rust vs. Zig

#536
post #62

Earlier quoted context omitted.

Especially since the second example only gives you a stringly-typed error. If you want to add 'proper' error types, wrapping them is just as difficult in Go and Rust (needing to implement `Error` in Go or `std::Error` in Rust). And, while we can argue about macro magic all day, the `thiserror` crate makes said boilerplate a non-issue and allows you to properly propagate strongly-typed errors with context when needed…

fmt.Errorf with %w directive in fact wraps an error. It will return an fmt.wrapError struct which can be inspected using `errors.Is`. So it's not stringly typed anymore.

I am fully aware of how fmt.Errorf works as well as what's inside the `errors` package in the Golang stdlib, as I do work with the language regularly.

In practice, this ends up with several issues (and I'm just as guilty of doing a bunch of them when I'm writing code not intended for public consumption, to be completely fair).

fmt.Errorf is stupid easy to use. There's a lot of Go code out there that just doesn't use anything else, and we really want to make sure we wrap errors to provide 'context' since there's no backtraces in errors (and nobody wants to force consuming code to pay that runtime cost for every error, given there's no standard way to indicate you want it).

errors.New can be used to create very basic errors, but since it gives you a single instance of a struct implementing `error` there's not a lot you can do with it.

The signature of a function only indicates that it returns `error`, we have to rely on the docs to tell users what specific errors they should expect. Now, to be fair, this is an issue for languages that use exception's - checked exceptions in Java notwithstanding.

Adding a new error type that should be handled means that consumers need to pay attention to the API docs and/or changelog. The compiler, linters, etc don't do anything to help you.

All of this culminates to an infuriating, inconsistent experience with error handling.

Re: Thoughts on Go vs. Rust vs. Zig

#537
post #506
post #500

Earlier quoted context omitted.

Compared to what? Because compared to go which has not one but 2 nulls, and an even more anemic type system it is surely much better. > the type system definitely doesn’t help you write correct programs. It surely helps significantly. You are just looking for even more from the type system, but that's another (fair) statement to make.

I started this thread criticizing Go. I think it’s a terribly verbose language that has ignored all language dev of the last half century. It’s why I continue to write Java. However, I don’t think that shields Java from its inability to make the language better. We still don’t have checked nulls and at this rate, even though there’s a draft JEP, I am not sure we will get them within this decade. The community still b…

Well, it's far from trivial to introduce such a change if you don't want to throw away 3 decades of code/assumptions. Of course null safety would be very welcome, I'm with you on this.

As for unchecked exceptions, that may be a bit of an "unreasonable ask". The only language that properly solves the problem are languages with effect types, which are an active research area. Every other language have either FP-like error values, or just unchecked exceptions (and there are terrible "solutions" like errno and whatever go does), or most likely both. E.g. Haskell will also throw exceptions, not everything is encoded as a value.

In my opinion both is the most reasonable approach, when you expect an error case, encode it as the return type (e.g. parsing an Integer is expected to fail). But system failures should not go there, exceptions solve it better (stuff like the million kind of connection/file system issues).

Re: Thoughts on Go vs. Rust vs. Zig

#538

Earlier quoted context omitted.

> But you only need about 5% of the concepts in that comment to be productive in Rust. The similar argument against C++ is applicable here: another programmer may be using 10% (or a different 5%) of the concepts. You will have to learn that fraction when working with him/her. This may also happen when you read the source code of some random projects. C programmers seldom have this problem. Complexity matters.

A similar problem applies to Go, just inverted. Take iteration. The vast majority of use cases for iterating over containers are map, filter, reduce. Go doesn't have these functions. That's very simple! All Go developers are aligned here: just use a for loop. There's no room for "10% of concepts corners", there's just that 1 corner. But, for loops get tedious. So people will make helper functions. Generic ones today,…

This was a big complaint against Forth. Every Forth site used a slightly different “standard” library.

Re: Thoughts on Go vs. Rust vs. Zig

#539
post #210

Earlier quoted context omitted.

> It's exceedingly rare to see any sort of global mutable state I know a bit of Rust, so you don't need to explain in details. How to use a local cache or db connection pool in Rust (both of them, IMO, are the right use case of global mutable state)?

Why does that have to be global? You can still pass it around. If you don't want to clobber registers, you can still put it in a struct. I don't imagine you are trying to avoid the overhead of dereferencing a pointer.

I think a better example might be logging. How is this typically solved in Rust? Do you have to pass a Logger reference to every function that potentially wants to log something? (In C++ you would typically have functions/macros that operate on a global logger instance.)

Re: Thoughts on Go vs. Rust vs. Zig

#540
post #453

Earlier quoted context omitted.

I’m not sure there’s anything clever that resolved the issues, they just settled on slow execution times by accepting a dynamic dispatch on generics.

Not according to this post: > Go generics combines concepts from "monomorphisation" (stenciling) and "boxing" (dynamic dispatch) and is implemented using GCshape stenciling and dictionaries. This allows Go to have fast compile times and smaller binaries while having generics. https://deepsource.com/blog/go-1-18-generics-implementation

Yes, note lack of mention of speed :)
Post reply on HN