Live data from Hacker News

An Honest Review of Go (2025)

benraz.dev

171–180 of 184 posts

Re: An Honest Review of Go (2025)

#171
post #152

Earlier quoted context omitted.

> e.g. net.OpError I have my own complain against Rust's error handlings, but I can't bring myself to praise what Go has been doing. One problem with the error interface is that you can't know exactly which error type will be returned, and the Go authors may add new error types form time to time. `net.OpError` itself is a great example for that, which is a newer type than net.Error. This style of error signalling is…

> This style of error signalling is best used when the error handling is binary, > either "No error, continue" or "Errored out, abort", but not branched > out path like "If encountered error A, do this. If encountered error B, > do that. Otherwise, abort". If I'm understanding you correctly, you can implement your example in a fully idiomatic way, and the stdlib does this in a bunch of places. Errors are just values,…

> If I'm understanding you correctly, you can implement your example in a fully idiomatic way, and the stdlib does this in a bunch of places.

I don't think you understood my point. Instead, I believe you launched your argument too quickly. Because if you continue reading, you'll see:

> ... but if you want to so the same in Go, there will be a nightmarish manual type discovery and tracking operation waiting for you.

Which should deter you from providing the example where you declared the `ErrFoo` and `ErrBar` type, since it's basically the "nightmarish manual type discovery" scenario unfolding in basically realtime.

But let's continue with the example to make my argument more full:

Let's say one day the `ErrFoo` and `ErrBar` type is not sufficient, so you declares another type `Err2000`. Now what happens down stream?

Since the some function may just return an error interface, there's no way for the end user to know you've added that error type. From their prospective, everything is unchanged, and still compiles just fine.

If the user's code is relying on the exact error type for branching, then they have to perform "manual type discovery and tracking" to monitor the changes to your code (and everything above it, really) to ensure that the branching is still done correctly.

In Rust however, since error is a emun, all error conditions must be handled or explicitly ignored during a `match`, if you write something like

    match File::open("filename.txt") {
        Ok(file) => {}
        Err(io_err) => match io_err.kind() {
            ErrorKind::IsADirectory => {} // Notice how not every error type is handled
        },
    }
The compiler will just stand up and make your ass weep. Meaning if you add some new error conditions, your user will know that for sure when they compile.

(BTW, `std::io::ErrorKind` is indeed `non_exhaustive`, but you the maker of the error type still have to explicitly/intentionally declare it to make it non_exhaustive)

So, let's read my comment again:

> Rust's error handling encourages such branched out handlings by default, but if you want to so the same in Go, there will be a nightmarish manual type discovery and tracking operation waiting for you.

(Also, "by default" is there to indicate that yeah you can boxing a `std::error:Error` trait in Rust, then you ended up with something like how Go handles error. I know.)

Re: An Honest Review of Go (2025)

#172
post #25

I'd encourage the author to spend more time learning Go. They've come to incorrect conclusions -- especially regarding errors. Read more of the stdlib to see how powerful they can be, e.g. net.OpError: https://cs.opensource.google/go/go/+/refs/tags/go1.25.5:src/... > The user now has an interface value error that the only thing > they can do is access the string representation of ... The only > resort the consumer of…

You're right, but I still think they have a point. What I miss from `error.Is/As` is exhaustive matching. I'd love a way to statically guarantee I haven't missed an important error type. It really comes back to the absence of sum types.

Exhaustive matching is terrible for backward/forward compatibility.

Re: An Honest Review of Go (2025)

#173
post #25

Earlier quoted context omitted.

You're right, but I still think they have a point. What I miss from `error.Is/As` is exhaustive matching. I'd love a way to statically guarantee I haven't missed an important error type. It really comes back to the absence of sum types.

Exhaustive matching is terrible for backward/forward compatibility.

Yeah, your error handling should not always be backwards/forwards compatible. If the new version of a library is throwing a new kind of error, that library's clients need to be updated. Being able to break your clients when necessary is a feature & not a bug. Of course, if you don't need to break your clients, simply don't change your ErrorKind enum.

Backwards/forwards compatibility matters a lot across service boundaries. I'm not saying Protobuf needs to be exhaustively matchable. But native types from a static library? You want to be able to match those exhaustively.

Re: An Honest Review of Go (2025)

#174
post #171

Earlier quoted context omitted.

> This style of error signalling is best used when the error handling is binary, > either "No error, continue" or "Errored out, abort", but not branched > out path like "If encountered error A, do this. If encountered error B, > do that. Otherwise, abort". If I'm understanding you correctly, you can implement your example in a fully idiomatic way, and the stdlib does this in a bunch of places. Errors are just values,…

> If I'm understanding you correctly, you can implement your example in a fully idiomatic way, and the stdlib does this in a bunch of places. I don't think you understood my point. Instead, I believe you launched your argument too quickly. Because if you continue reading, you'll see: > ... but if you want to so the same in Go, there will be a nightmarish manual type discovery and tracking operation waiting for you. W…

  > nightmarish manual type discovery
Your criticism here is of a lack of Sum-types in Golang, not the approach to errors as values. It's just a different philosophy to Rust.

Go maintains a strict backwards-compatibility guarantee (*love* this), and using sum types for errors would mean, at least in the stdlib, either (a) no new errors can be introduced, or (b) new versions of Go would break builds when new errors are introduced.

  > Since the some function may just return an error interface, there's
  > no way for the end user to know you've added that error type. From
  > their prospective, everything is unchanged, and still compiles just fine.
Personally, I value my code compiling in the future over more explicit error handling. New errors will hit my catch-all branch, and I can special-case them later as I see fit. But it's a philosophy/values thing.

As an aside, I very rarely find myself actually wanting a sum type for errors. I usually want to check for a few specific errors, but I almost always want a catch-all for truly unexpected stuff. Sum typed errors in any complex system often end up needing a `.other()`-style case anyway because in the real world so much can go wrong.

Rust is by no means perfect either. The `?` operator steers you in the direction of ignoring errors rather than thinking about them, which I think leads to worse outcomes (i.e. that Cloudflare outage)

Re: An Honest Review of Go (2025)

#175
post #171

Earlier quoted context omitted.

> This style of error signalling is best used when the error handling is binary, > either "No error, continue" or "Errored out, abort", but not branched > out path like "If encountered error A, do this. If encountered error B, > do that. Otherwise, abort". If I'm understanding you correctly, you can implement your example in a fully idiomatic way, and the stdlib does this in a bunch of places. Errors are just values,…

> If I'm understanding you correctly, you can implement your example in a fully idiomatic way, and the stdlib does this in a bunch of places. I don't think you understood my point. Instead, I believe you launched your argument too quickly. Because if you continue reading, you'll see: > ... but if you want to so the same in Go, there will be a nightmarish manual type discovery and tracking operation waiting for you. W…

Sidenote: if you really need compile-time safety for errors, you can always use your function signature to achieve this.

  func DoThing(onFoo func(), onBar func()) { ... }
It's a bit horrible, but does solve for the rare occasion when certain scenarios must be handled, and makes it clear that the change is a breaking one. Personally never needed this though.

Re: An Honest Review of Go (2025)

#176
post #55
post #26

> difficulty of writing if err != nil Literally the simplest way to deal with errors (cognitively and character wise). Since AI autocomplete entered the scene, typing this repetitive (for a reason) pattern became not a problem at all (I'm not even talking about post Claude Code era) > The only resort the consumer of this library has is to parse the string value of this error for useful information. Well, no. See for…

> typing this repetitive (for a reason) pattern became not a problem at all Code is read 10x more than it is written. The noise this pattern introduces inhibits reading and rapid comprehension. Things are getting marginally better now that go has errors.Is and errors.As, and also now that go is starting to get some functional iterators. But go is one of the least quickly-understandable of the modern languages current…

> The noise this pattern introduces inhibits reading and rapid comprehension.

Nonsense. We don't read letters, nor even words, we see patterns. if err != nil {} is easy to skip over visually.

Re: An Honest Review of Go (2025)

#177
post #113

Earlier quoted context omitted.

Your last sentence makes sense only if you consider “happy path” to be what you call “understanding how function works”. A lot of programmers are annoyed that Go forces them to think about “error path” as equally important. Later many say that Go forces you to be the better programmer. But it takes time. In any case, saying “language has a problem” is a wrong frame for thinking about this design choice.

This is something gophers constantly repeat, but it is entirely a self-inflicted issue that other languages more modern than C don’t actually struggle with. When in Rust, it is explicitly clear when and where errors can come up, and what types of errors I have to deal with. It is far less clear in golang, since there is no help from the type system when I’m using errors.Is and errors.As. Not being verbose doesn’t mak…

> other languages more modern than C

> When in Rust

A language newer than Go isn't just "more modern than C".

Re: An Honest Review of Go (2025)

#178
post #135

Earlier quoted context omitted.

That's not a straw man, it's a valid reply to your first argument about "error handling code being an obstacle to understanding how function works". Why it makes you angry and makes you resort to ad hominem, I don't know, but that's clearly the end of the discussion.

No, it is a knee-jerk response that invariably assumes that a belief that golang’s specific approach is bad is equivalent to believing that errors are of secondary importance, and assumes that anyone who disagrees with the golang approach just doesn’t get how important it is to deal with errors. You would rather dismiss other perspectives out of hand than actually reflect on how your chosen language might evolve. In…

The only knee-jerking here is this: "When it takes five times as long to figure out how a function actually works".

Don't be surprised by receiving low quality responses when you start off the discussion with ridiculous hyperboles and show poor understanding of the issue at hand.

Re: An Honest Review of Go (2025)

#179

Earlier quoted context omitted.

When I use Go, I find I can get more real work done with only the standard library than with any other language I've used (including Java, although that was quite a while ago now). It may not have the most stuff, it is more focused - but I'm okay with esoteric stuff not being in there since the tradeoff seems to be that there are high-quality implementations of e.g. an HTTP client and server, crypto functions, a unit…

Python's standard library seems comparable IME

I've found it way more hit and miss in terms of quality. From some examples I gave:

- JSON: Yup, stdlib is good here

- HTTP client: There is urllib.request, although people maybe reach for requests more.

- HTTP server: No, it's just some super basic thing (it's not even 'nice' to write for local dev). Would never let anything here near production.

- Unit testing: Yes, I think unittest is pretty good (I actually like it a lot, and think pytest is overrated).

- Crypto functions: No, not equivalent. There is hashlib but basically anything else you're pointed at pycrypto or cryptography with all the associated ecosystem nonsense (oh I need a Rust compiler now? Great...).

The difference is Python _has_ all of these things in the standard library, but the quality is super mixed, and not all of them are suitable for real use.

Re: An Honest Review of Go (2025)

#180

I think I know why Go ended up without good enum support. (Disclaimer, formerly worked at Google and used proto/grpc/go there and now in my own startup in github.com/accretional/collector which tries to address this problem with a type registry and fully reflective API. Not privy to the full history, just reasoning.) Proto is designed so that messages can be deserialized into older/previous proto definitions by clien…

An interesting theory, however I rather suspect it is basically because Limbo had a similar concept for pseudo-enums.

I allowed (e.g.) a syntax like:

    M0, M1, M2, M3, M4: con (1
That was taken from one of the Limbo papers.
Post reply on HN