Readit News logoReadit News
threemux · 7 months ago
If you feel the need (as many have in this thread) to breezily propose something the Go Team could have done instead, I urge you to click the link in the article to the wiki page for this:

https://go.dev/wiki/Go2ErrorHandlingFeedback

or the GitHub issue search: https://github.com/golang/go/issues?q=+is%3Aissue+label%3Aer...

I promise that you are not the first to propose whatever you're proposing, and often it was considered in great depth. I appreciate this honest approach from the Go Team and I continue to enjoy using Go every day at work.

hackingonempty · 7 months ago
The draft design document that all of the feedback is based on mentions C++, Rust, and Swift. In the extensive feedback document you link above I could not find mention of do-notation/for-comprehensions/monadic-let as used Haskell/Scala/OCaml. I didn't find anything like that in the first few pages of the most commented GitHub issues.

You make it out like the Go Team are programming language design wizards and people here are breezily proposing solutions that they must have considered but lets not forget that the Go team made the same blunder made by Java (static typing with no parametric polymorphism) which lies at the root of this error handling problem, to which they are throwing up their hands and not fixing.

munificent · 7 months ago
I think Go should have shipped with generics from day one as well.

But you breezily claiming they made the same blunder as Java omits the fact that they didn't make the same blunder as Rust and Swift and end up with nightmarish compile times because of their type system.

Almost every language feature has difficult trade-offs. They considered iteration time a priority one feature and designed the language as such. It's very easy for someone looking at a language on paper to undervalue that feature but when you sit down and talk to users or watch them work, you realize that a fast feedback loop makes them more productive than almost any brilliant type system feature you can imagine.

9rx · 7 months ago
> lets not forget that the Go team made the same blunder made by Java

To be fair, they were working on parametric polymorphism since the beginning. There are countless public proposals, and many more that never made it beyond the walls of Google.

Problem was that they struggled to find a design that didn't make the same blunder as Java. I'm sure it would have been easy to add Java-style generics early on, but... yikes. Even the Java team themselves warned the Go team to not make that mistake.

platinumrad · 7 months ago
Even Rust and F#[1] don't have (generalized) do notation, what makes it remotely relevant to a decidedly non-ML-esque language like Go?

[1] Okay fine, you can fake it with enough SRTPs, but Don Syme will come and burn your house down.

yusina · 7 months ago
It fascinates me that really smart and experienced people have written that page and debated approaches for many years, and yet nowhere on that page is the Haskell-solution mentioned, which is the Maybe and Either monads, including their do-notation using the bind operator. Sounds fancy, intimidating even, but is a very elegant and functionally pure way of just propagating an error to where it can be handled, at the same time ensuring it's not forgotten.

This is so entrenched into everybody writing Haskell code, that I really can't comprehend why that was not considered. Surely there must be somebody in the Go community knowing about it and perhaps appreciating it as well? Even if we leave out everybody too intimidated by the supposed academic-ness of Haskell and even avoiding any religios arguments.

I really appreciate the link to this page, and overall its existence, but this really leaves me confused how people caring so much about their language can skip over such well-established solutions.

jchw · 7 months ago
I don't get why people keep thinking it was forgotten; I will just charitably assume that people saying this just don't have much background on the Go programming language. The reason why is because implementing that in any reasonable fashion would require massive changes to the language. For example, you can't build Either/Maybe in Go (well, of course you can with some hackiness, but it won't really achieve the same thing) in the first place, and I doubt hacking it in as a magic type that does stuff that can't be done elsewhere is something the Go developers would want to do (any more than they already have to, anyway.)

Am I missing something? Is this really a good idea for a language that can't express monads naturally?

Yoric · 7 months ago
Well, Rust's `?` was initially designed as a hardcoded/poor man's `Either` monad. They quote `?` as being one of the proposals they consider, so I think that counts?

Source: I'm one of the people who designed it.

joaohaas · 7 months ago
It was not forgotten. Maybe/Either and 'do-notation' are literally what Rust does with Option/Result and '?', and that is mentioned a lot.

That said as mentioned in a lot of places, changing errors to be sum types is not the approach they're looking for, since it would create a split between APIs across the ecosystem.

9rx · 6 months ago
> and yet nowhere on that page is the Haskell-solution mentioned

What do you mean? Much of the discussion around errors from above link is clearly based on the ideas of Haskell/monads. Did you foolishly search for "monad" and call it a day without actually reading it in full to reach this conclusion?

In fact, I would even suggest that the general consensus found there is that a monadic-like solution is the way forward, but it remains unclear how to make that make sense in Go without changing just about everything else about the language to go along with it. Thus the standstill we're at now.

anentropic · 7 months ago
It's probably already answered somewhere, but I am curious why it's such a problem in Go specifically, when nearly every language has something better - various different approaches ... is the problem just not being able to decide / please everyone, or there's something specific about Go the language that means everyone else's solutions don't work somehow?
mamcx · 6 months ago
> is the problem just not being able to decide / please everyone,

Reading this article? in fact yes(?):

> After so many years of trying, with three full-fledged proposals by the Go team and literally hundreds (!) of community proposals, most of them variations on a theme, all of which failed to attract sufficient (let alone overwhelming) support, the question we now face is: how to proceed? Should we proceed at all?

> We think not.

This is a problem of the go designers, in the sense that are not capable to accept the solutions that are viable because none are total to their ideals.

And never will find one.

____

I have use more than 20 langs and even try to build one and is correct that this is a real unsolved problem, where your best option is to pick one way and accept that it will optimize for some cases at huge cost when you divert.

But is know that the current way of Go (that is a insignificant improvement over the C way) sucks and ANY of the other ways are truly better (to the point that I think go is the only lunatic in town that take this path!), but none will be perfect for all the scenarios.

ok_dad · 7 months ago
Go has specific goals like not hiding control flow. This would go against those goals, at least the ways people have thought to do it so far.
skywhopper · 7 months ago
The thing is, it’s not actually a major problem. It’s the thing that gets the most complaints for sure, and rubs folks from other languages the wrong way often. But it’s an intentional design that is aware of its tradeoffs. As a 10 year Go veteran, I strongly prefer Go’s approach to most other languages. Implicit control flow is a nightmare that is best avoided, imo.

It’s okay for Go to be different than other languages. For folks who can’t stand it, there are lots of other options. As it is, Go is massively successful and most active Go programmers don’t mind the error handling situation. The complaints are mostly from folks who didn’t choose it themselves or don’t even actually use it.

The fact that this is the biggest complaint about Go proves to me the language is pretty darn incredible.

munificent · 7 months ago
I think the two big things for Go are:

1. Minimalism.

Go has always had an ethos of extreme minimalism and have deliberately cultivated an ecosystem and userbase that also places a premium on that. Whereas, say, the Perl ecosystem would be delighted to have the language add one or seven knew ways of solving the same problem, the Go userbase doesn't want that. They want one way to do things and highly value consistency, idiomatic code, and not having to make unnecessary implementation choices when programming.

In every programming language, there is a cost to adding features, but that cost is relatively higher in Go.

2. Concurrency.

Concurrency, channels, and goroutines are central to the design of the language. While I'm sure you can combine exception handling with CSP-based concurrency, I wouldn't guarantee that the resulting language is easy to use correctly. What happens when an uncaught exception unwinds the entire stack of a goroutine? How does that affect other goroutines that it spawned or that spawned it? What does it do to goroutines that are waiting on channels that expect to hear from it?

There may be a good design there, but it may also be that it's just really really hard to reason about programs that heavily use CSP-style concurrency and exceptions for error handling.

The Go designers cared more about concurrency than error handling, so they chose a simpler error handling model that doesn't interfere with goroutines as much. (I understand that panics complicate this story. I'm not a Go expert. This is just what I've inferred from the outside.)

hackingonempty · 7 months ago
The language is designed for Google, which hires thousands of newly graduated devs every year. They also have millions of lines of code. In this environment they value easy of onboarding devs and maintaining the codebase over almost everything else. So they are saddled with bad decisions made a long time ago because they are extremely reluctant to introduce any new features and especially breaking changes.
philosophty · 7 months ago
This is a common theme with criticisms of Go.

Relative amateurs assuming that the people who work on Go know less about programming languages than themselves, when in almost all cases they know infinitely more.

The amateur naively assumes that whichever language packs in the most features is the best, especially if it includes their personal favorites.

The way an amateur getting into knife making might look at a Japanese chef's knife and find it lacking. And think they could make an even better one with a 3D printed handle that includes finger grooves, a hidden compartment, a lighter, and a Bluetooth speaker.

Yoric · 7 months ago
FWIW, I have designed several programming languages and I have contributed (small bits) to the design of two of the most popular programming languages around.

I understand many of Go's design choices, I find them intellectually pleasing, but I tend to dislike them in practice.

That being said, my complaints about Go's error-handling are not the `if err != nil`. It's verbose but readable. My complaints are:

1. Returning bogus values alongside errors.

2. Designing the error mechanism based on the assumptions that errors are primarily meant to be logged and that you have to get out of your way to develop errors that can actually be handled.

throwawaymaths · 7 months ago
To be fair there are lots of people who have used multiple programming languages at expert levels that complain about go - in the same ways - as well! They might not be expert programming language designers, but they have breadth of experience, and even some of them have written their own programming languages too.

Assuming that all complainants are just idiots is purely misinformed and quite frankly a bit of gaslighting.

Deleted Comment

38 · 6 months ago
that is weird that they call it a Wiki when it is not a wiki any more - you have to submit changes for approval
arccy · 6 months ago
unfortunately too much spam but the review process is much lighter
afiori · 6 months ago
Protected pages are a feature of all wikis
mamcx · 6 months ago
The problem is that error handling is far more complex than you think at first.

The idea that "the happy path is the most common" is a total lie.

    a + b
CAN fail. But HOW that is the question!

So, errors are everywhere. And you must commit to a way to handle it and no is not possible, like no, not possible to satisfy all the competing ideas about it.

So there is not viable to ask the community about it, because:

    a + b
CAN fail. But HOW change by different reasons. And there is not possible to have a single solution for it, precisely because the different reasons.

So, you pick a side and that is.

pie_flavor · 7 months ago
You draw up a list of checkboxes, you debate each individual point until you can check them off, you don't uncheck them unless you have found a showstopping semantics error or soundness hole. Once it is complete it is implemented and then everyone who had opinions about whether it should be spelt `.await` or `/await` or `.await!()` vanishes back into the woodwork from whence they came. Where's the disconnect?

Rust works like this. Sometimes an issue can be delayed for over a decade, but eventually all the boxes are checked off and it gets stabilized in latest nightly. If Go cannot solve the single problem everyone immediately has with the language, despite multiple complete perfect proposals on how to do it, simply because they cannot pick between the proposals and are waiting for people to stop bikeshedding, then their process is a farce.

Thaxll · 7 months ago
"despite multiple complete perfect proposals on how to do it"

There is no such a thing.

arccy · 7 months ago
and this is how rust gains its reputation as an ugly to read language with inconsistent syntax: design by committee
codedokode · 7 months ago
Golang is also ugly, for example some fields start with a capital letter and some do not.

Also I don't understand how to implement transparent proxies in Go for reactive UI programming.

jeremyjh · 7 months ago
Whereas Go has taken the approach of designing everything "later".
olalonde · 7 months ago
Isn't "design by popular vote" an extreme version of "design by committee"? Go won't implement error handling syntax because it can't reach community consensus.
wtetzner · 7 months ago
Ugly is subjective, but which part of the syntax is inconsistent?
zaptheimpaler · 7 months ago
This is the kind of criticism made by people who've spent less than a few days working with a language. Just glancing at some code from a distance. There's nothing actually wrong with it besides being foreign from what you are used to. After you gain some familiarity, it doesn't look ugly or beautiful it just looks like syntax.
dcow · 7 months ago
This might be a valid point if Go wasn’t an atrociously ugly and verbose language with some of the worst design out there.
kiitos · 7 months ago
Programming languages are designed systems, they need to make sense holistically. They're not simply collections of tick-boxed features that are expected to be added once their tick-box requirements are satisfied.
stouset · 6 months ago
> Programming languages are designed systems, they need to make sense holistically.

Of all the languages in common use, golang is the one that makes the least sense holistically. Return values are tuples, but there's nothing that lets you operate on them. Enums aren't actually limited to the values you define, so there's no way to ensure your switch cases are exhaustive when one is added in the future. Requiring meaningful zero values means that your error cases return valid, meaningful values that can accidentally be used when they return with an error.

827a · 7 months ago
This opinion is clearly far more arguable than you might think.
TZubiri · 6 months ago
!RemindMe in 25 years.

Did Rust become a clusterfuck like C++?

Is Go as timeless as it was during release?

hu3 · 7 months ago
> Go cannot solve the single problem everyone immediately has with the language...

What? Survey says 13% mentioned error handling.

And some people actually do prefer it as is.

https://go.dev/blog/survey2024-h1-results

judofyr · 7 months ago
13% mentioned that error handling was the biggest challenge with using Go. This was not a multiple choice question, but you had to pick one answer. We don't know how many people would consider it challenging. (This is typically why you have a 1-10 scale per choice.)
joaohaas · 7 months ago
This doesn't mean the rest of the 87% enjoy it. Honestly, I'd rather the next survey included a question "are you satisfied with the current error handling approach"
827a · 7 months ago
That survey specifically asked for the "biggest" challenge. One could make a compelling argument for the survey answer "learning how to write Go effectively" being an extremely bad option to put on a survey, because it at-least partially catch-alls every other answer. Its no wonder it got first place.
GoatInGrey · 7 months ago
> complete perfect

This is entirely subjective and paints the Go community as being paradoxical, simultaneously obstinate and wanting change.

The disappointing reality is that Go's error handling is the least terrible option in satisfying the language design ethos and developers writing Go. I have a penchant for implementing V's style of error handling, though I understand why actually implementing it wouldn't be all sunshine and rainbows.

pie_flavor · 7 months ago
No, actually, an operator that's essentially a macro for this entirely boilerplate operation would be less terrible, exactly the same decision Rust made for the exact same reason. So would Cox's proposal, so would others. Doing nothing, as a permanent solution, because you can't figure out which of the better things you should do, is not a virtue. You may be confusing it with the virtue of holding out on simpler solutions while a better solution is implemented, or the virtue of not solving things which aren't problems, but this is a problem and they have intentionally blocked the solution indefinitely.
bravesoul2 · 7 months ago
If Haskell was mainstream and everyone piled in and complained that objects were immutable and it adds so much noise having to deal with that using lenses or state monads or whatever, do we go with democracy or do we say wait.... maybe Haskell was meant to be like this, there are reasons something is a seperate language.
_jab · 7 months ago
I once had a Go function that, unusually, was _expecting_ an error to be returned from an inner function, and so had to return an error (and do some other processing) if none was returned by the inner function, and return nil if the inner function did return an error.

In a nutshell, this meant I had to do `if err == nil { // return an error }` instead of `if err != nil { ... }`. It sounds simple when I break it down like this, but I accidentally wrote the latter instead of the former, and was apparently so desensitized to the latter construct that it actually took me ages to debug, because my brain simply did not consider that `if err != nil` was not supposed to be there.

I view this as an argument in favor of syntactic sugar for common expressions. Creating more distinction between `if err != nil` (extremely common) and `if err == nil` (quite uncommon) would have been a tangible benefit to me in this case.

adamrt · 7 months ago
Any time I write "if err == nil" I write // inverted just to make it stick out. It would be nice if it was handled by the language but just wanted to share a way to at least make it a bit more visible.

    if err == nil { // inverted
        return err
    }

hnlmorg · 7 months ago
I do something similar. I leave a comment but with a short comment why it’s inverted.

It’s usually pretty obvious why: eg

    if err == nil { 
         // we can exit early because we don’t need to keep retrying
But it at least saves me having to double check the logic of the code each time I reread the code for the first time in a while.

macintux · 7 months ago
I know diddly/squat about Go, but from similar patterns in aeons past, would "nil == err" work as a way to make it stand out?
umanwizard · 6 months ago
Something slightly more elegant (in my subjective opinion) you could do is write

  if !(err != nil) {

vhcr · 7 months ago
Would be nice if code editors colored it differently so it's easier to see.
prerok · 7 months ago
return nil

would be clearer, I think. Seems like it's the same but would color differently in my editor.

9rx · 7 months ago
Of course, `if fruit != "Apple" { ... }` would leave you in the exact same situation. Is there a general solution to improving upon this? Seeing it as an error problem exclusively seems rather misguided. After all, there is nothing special or unique about errors. They're just state like any other.
adamrt · 7 months ago
I think its more of a comment that "err != nil" is used in the vast majority of cases, so you start to treat it as noise and skim it.
purpleidea · 7 months ago
This is actually an argument against the syntactic changes. Because now if you have the common `if err == nil { return ... }` pattern, then you have _that_ "littering" your code, instead of the syntax.

The current solution is fine, and it seems to be only junior/new to golang people who hate it.

Everyone I know loves the explicit, clear, easy to read "verbose" error handling.

scubbo · 7 months ago
> then you have _that_ "littering" your code, instead of the syntax.

Yes, exactly. The unusual thing _should_ look unusual.

derefr · 7 months ago
Just as a devil's-advocate argument, an IDE + font could syntax-highlight + ligature `if err != nil` (only under Golang syntax mode) into a single compact heiroglyph and fade it into the background — which would in turn make anything that differs from that exact string (like `if err == nil`) now pop out, due to not being rendered that way.
saghm · 6 months ago
The same logic could apply to the oppositions they cited to the `try` function though; an editor could easily make it stick out to alleviate it blending in when nested inside blocks. This is exactly why nobody ever accidentally confuses `.await` in Rust for a struct field even though from a plaintext perspective it's syntactically identical. If you're going to utilize the editor to carry the heavy weight, you might as well just pick literally any new syntax that replaces all of the extra typing with something more terse.
skybrian · 7 months ago
Good point. Perhaps it could also be solved in an editor with a collapsed notation like ‘if err … {‘
matthewmueller · 6 months ago
I like nil == err for this case
TZubiri · 6 months ago
Nothingburger here, you had a bug, and you fixed it.

All is well, no need to question your language or the meaning of life.

When you make a mistake irl or trip over when walking, do you reconsider you DNA and submit a patch to God?

Sometimes you just gotta have faith in the language and assume it like an axiom, to avoid wasting energy fighting windmills.

I'm not a deep Go programmer, but I really enjoy how it's highly resistant to change and consistent across it's 15 years so far.

jamamp · 7 months ago
I like Go's explicit error handling. In my mind, a function can always succeed (no error), or either succeed or fail. A function that always succeeds is straightforward. If a function fails, then you need to handle its failure, because the outer layer of code can not proceed with failures.

This is where languages diverge. Many languages use exceptions to throw the error until someone explicitly catches it and you have a stack trace of sorts. This might tell you where the error was thrown but doesn't provide a lot of helpful insight all of the time. In Go, I like how I can have some options that I always must choose from when writing code:

1. Ignore the error and proceed onward (`foo, _ := doSomething()`)

2. Handle the error by ending early, but provide no meaningful information (`return nil, err`)

3. Handle the error by returning early with helpful context (return a general wrapped error)

4. Handle the error by interpreting the error we received and branching differently on it. Perhaps our database couldn't find a row to alter, so our service layer must return a not found error which gets reflected in our API as a 404. Perhaps our idempotent deletion function encountered a not found error, and interprets that as a success.

In Go 2, or another language, I think the only changes I'd like to see are a `Result<Value, Failure>` type as opposed to nillable tuples (a la Rust/Swift), along with better-typed and enumerated error types as opposed to always using `error` directly to help with error type discoverability and enumeration.

This would fit well for Go 2 (or a new language) because adding Result types on top of Go 1's entrenched idiomatic tuple returns adds multiple ways to do the same thing, which creates confusion and division on Go 1 code.

barrkel · 7 months ago
My experience with errors is that error handling policy should be delegated to the caller. Low level parts of the stack shouldn't be handling errors; they generally don't know what to do.

A policy of handling errors usually ends up turning into a policy of wrapping errors and returning them up the stack instead. A lot of busywork.

XorNot · 7 months ago
At this point I make all my functions return error even if they don't need it. You're usually one change away from discovering they actually do.
billmcneale · 7 months ago
> If a function fails, then you need to handle its failure

And this is exactly where Go fails, because it allows you to completely ignore the error, which will lead to a crash.

I'm a bit baffled that you correctly identified that this is a requirement to produce robust software and yet, you like Go's error handling approach...

haiku2077 · 7 months ago
On every project I ship I require golangci-lint to pass to allow merge, which forces you to explicitly handle or ignore errors. It forbids implicitly ignoring errors.

Note that ignoring errors doesn't necessarily lead ti a crash; there are plenty of functions where an error won't ever happen in practice, either because preconditions are checked by the program before the function call or because the function's implementation has changed and the error return is vestigal.

pphysch · 7 months ago
> which will lead to a crash

No it won't. It could lead to a crash or some other nasty bug, but this is absolutely not a fact you can design around, because it's not always true.

stock_toaster · 6 months ago
I just want borgo[1] syntax to be the Go 2 language. A man can dream...

[1]: https://borgo-lang.github.io/ | https://github.com/borgo-lang/borgo

ignoramous · 7 months ago
At this rate, I suspect Go2 is an ideas lab for what's never shipping.
LinXitoW · 7 months ago
I have to ask, in comparison to what do you like it? Because every functional language, many modern languages like Rust, and even Java with checked exceptions offers this.

Hell, you can mostly replicate Gos "error handling" in any language with generics and probably end up with nicer code.

If your answer is "JavaScript" or "Python", well, that's the common pattern.

jamamp · 6 months ago
In primarily throwable languages, it's more idiomatic to not include much error handling throughout the stack but rather only at the ends with a throw and a try/catch. Catching errors in the middle is less idiomatic.

Whereas in Go, the error is visible everywhere. As a developer I see its path more easily since it's always there, and so I have a better mind to handle it right there.

Additionally, it's less easy to group errors together. A try/catch with multiple throwable functions catches an error...which function threw it though? If you want to actually handle an error, I'd prefer handling it from a particular function and not guessing which it came from.

Java with type-checked exceptions is nice. I wish Swift did that a bit better.

maxwellg · 7 months ago
This is the right move for Go. I have grown to really love Go error handling. I of course hated it when I was first introduced to the language - two things that changed that:

- Reading the https://go.dev/blog/errors-are-values blog post (mentioned in the article too!) and really internalizing it. Wrote a moderately popular package around it - https://github.com/stytchauth/sqx

- Becoming OK with sprinkling a little `panic(err)` here and there for truely egregious invalid states. No reason forcing all the parent code to handle nonsense it has no sense in handling, and a well-placed panic or two can remove hundreds of error checks from a codebase. Think - is there a default logger in the ctx?

masklinn · 7 months ago
This is just sad. Neither of your supports have anything to do with how dismal Go's error handling is, and neither would be worsened in any way by making it better. If anything they would be improved.
pas · 7 months ago
Even PHP has better error handling with the levels, and the @ (at operator) to suppress errors at a callsite.

Even bash has -e :)

waynerad · 6 months ago
I've been programming in PHP since 2002 and Go since 2011. At work I work primarily with two large PHP codebases, 1 about ~half a million lines of code, and the other about ~3 million lines of code. I'm porting PHP code to Go and writing some new code, like cron jobs, in Go. There's about 55,000 lines of Go code at this point.

What I have learned in this time is: Go is a very good, very high quality, very well designed language. PHP is a terrible, terrible, terrible language -- at least if your mission is creating highly reliable software. If your goal is rapid prototyping of the visual appearance of a webpage, PHP is good for that -- that's what it was originally invented for, back in the 1990s, and it's still the best language for that specific use case.

In PHP, if you are looking at a line of code, it's impossible to tell what happens if an error occurs. There's an error_reporting level that can be changed anywhere else in the codebase at any time. So an error might be thrown in the user's face, or not -- depending on the error_reporting level and what the error is. This can also interact with output buffering. (I guess if the "@" sign is present, at least then you know output of an error message is suppressed.) There's a error_handler function that could be set. Oh but it gets worse! Even if you have the entire codebase and can scour every line of code, and trace every possible execution path, you still can't tell what will happen if an error occurs at the line of code you are looking at -- because there are php.ini settings that come into play!

This is without even mentioning the try/catch exception handling system, which PHP also has, and that you think is such a good thing. So if an error happens on the line you are looking at, execution may switch to somewhere above you in the call stack. But (at least in theory) if you have the complete source code, you can figure out all the places where that might be.

This is an absolute nightmare if you care about reliability. The worst thing you could possibly have in a programming language if you care about reliability is to be unable to tell what happens when an error occurs and reason about the program's behavior.

Contrast this with Go. In Go, I can know what happens if an error occurs because it's right there. There's an "if err != nil {..." block that tells me exactly what happens.

By porting code to Go, I've been able to increase the reliability tremendously. The thing about go, is that using Go doesn't automatically give you reliability -- you have to learn the techniques and tools in Go that give you reliability. But once you do that, the quality of software you can produce is vastly higher.

I've spent the last decade or so learning this. Learning how to structure the code to make maximum use of the compiler's static type checking to catch errors. Making use of the static analysis tools that catch errors. Using a combination of errcheck and staticcheck (mentioned elsewhere in this discussion), you can guarantee every err return value is actually checked and handled. So the oft-cited language flaw of Go allowing you to forget to handle errors becomes a non-issue. Go has built-in support for unit tests and the excellent polymorphism system makes tests easier to make than in PHP. And, getting back to the topic at hand, in Go, I created my own system for handling the err values that get returned so junk errors created by hackers trying to find security vulnerabilities on our server get filtered out, while actual errors that I need to fix get logged and brought swiftly to my attention. Can you do something similar in PHP? In theory, perhaps, but we are to afraid of breaking things to try to massively refactor our ~2 million lines of PHP code in order to do that. So we have various log files and database entries that get clogged with errors, and sometimes we notice errors that we need to fix, but usually we don't. Usually we don't know anything is wrong until we get customer complaints on the Help Desk.

Anyway, I'm glad the Go team rejected the idea of changing Go's error handling. Is Go's error handling verbose, tedious, and repetitive? Yes. But it's also unambiguous and gives you total control over how your program responds when an error occurs, which is exactly what you want if you want to write software with the maximum reliability.

Don't just take my word for it. Notice how lots of "cloud infrastructure" is written in Go. Kubernetes is written in Go, Docker is written in Go, Terraform is written in Go, etc.

Yes, I know some people, like Facebook, have managed to write decently reliable code in PHP. But I suspect Facebook has thrown massive amounts of dollars and people at the problem. Various other PHP projects, like certain website creation systems that shall not be named, have a reputation for being buggy.

Sorry, I couldn't let you say PHP has better error handling without a response. It may be "better" for having more compact, prettier looking code. But it's not better for writing actually reliable software. If you want to write actually reliable software, Go is way better.

bravesoul2 · 7 months ago
Me too. I'll take the higher Loc for the greater certainty of what is going on.

I thought it was clever in C# years ago when I first used to to grok all the try/catch/finally flows including using and nested versions and what happens if an error happens in the catch and what if it happens in the finally and so on. But now I'd rather just not think about that stuff.

Deleted Comment

evertedsphere · 7 months ago
rust-style "sum type" errors are values too
9rx · 6 months ago
But, of course, makes the incorrect assumption that <T, E> are dependent variables. The idiomatic Go approach is much more representative of reality. Tradeoffs, as always.
Mond_ · 7 months ago
I really don't like how this article claims that the primary issue with Go's error handling is that the syntax is too verbose. I don't really care about that.

How about:

- Errors can be dropped silently or accidentally ignored

- function call results cannot be stored or passed around easily due to not being values

- errors.Is being necessary and the whole thing with 'nested' errors being a strange runtime thing that interacts poorly with the type system

- switching on errors being hard

- usage of sentinel values in the standard library

- poor interactions with generics making packages such as errgroup necessary

Did I miss anything?

closeparen · 7 months ago
90% of working professionally in Go is contriving test cases to achieve statement coverage over each error return branch, something no one would ever do in a language with exceptions.
cedws · 6 months ago
Test coverage as a target metric is stupid.
ncruces · 6 months ago
It this really bothers you, convince your team to use courtney and focus on the more relevant error branches: the ones where (1) a novel error value is produced or (2) handled rather than those that simply bubble it up.

https://github.com/dave/courtney

neild · 7 months ago
> I really don't like how this article claims that the primary issue with Go's error handling is that the syntax is too verbose.

I don't believe this claim is made anywhere.

We've decided that we are not going to make any further attempts to change the syntax of error handling in the foreseeable future. That frees up attention to consider other issues (with errors or otherwise).

pas · 7 months ago
writing a small book on the topic and somehow missing it is the point, not that technically the text is claiming it, the subtext is doing it.
GiorgioG · 7 months ago
Just remember it took FOREVER for Go to support some form of generics. Go evolution happens at a glacial pace. That's a feature, not a bug....to many.
VirusNewbie · 7 months ago
Agreed, 100%.

We're both Googlers here and this is so disappointing to be let down again by the Go team.

SkepticalWhale · 6 months ago
I agree with item #1, but it can be mitigated somewhat with dev tools like errcheck: https://github.com/kisielk/errcheck?tab=readme-ov-file
tsimionescu · 7 months ago
> Going back to actual error handling code, verbosity fades into the background if errors are actually handled. Good error handling often requires additional information added to an error. For instance, a recurring comment in user surveys is about the lack of stack traces associated with an error. This could be addressed with support functions that produce and return an augmented error. In this (admittedly contrived) example, the relative amount of boilerplate is much smaller:

  [...] 
  if err != nil {
        return fmt.Errorf("invalid integer: %q", a)
    }
  [...] 
It's so funny to me to call "manually supplying stack traces" as "handling an error". By the Go team's definition of handling errors, exceptions* "automatically handle errors for you".

* in any language except C++, of course

teeray · 7 months ago
It's funny to me when people see screenfuls of stack traces and remark how clear and useful it is. Perhaps, but do you really need all that? At what cost to your logs? I'd much rather have a one-liner wrapped error that cuts through all the framework and runtime noise. Yes, I can trace just as effectively (usually better)--the wrapping is very greppable when done well. No, in over a decade of writing Go full time, I have never cared about a runtime function or the other usual verbose garbage in my call stack.
rwiggins · 7 months ago
> how clear and useful it is. Perhaps, but do you really need all that?

Do I need clear and useful things? Maybe not. Would I like to have them anyway? Yes.

Years ago, I configured a Java project's logging framework to automatically exclude all the "uninteresting" frames in stack traces. It was beautiful. Every stack trace showed just the path taken through our application. And we could see the stack of "caused-by" exceptions, and common frames (across exceptions) were automatically cut out, too.

Granted, I'm pretty sure logback's complexity is anathema to Go. But my goodness, it had some nice features...

And then you just throw the stack trace in IntelliJ's "analyze stacktrace" box and you get clickable links to each line in every relevant file... I can dream.

> the wrapping is very greppable when done well

Yeah, that's my other problem with it. _When done well._ Every time I write an `if err != nil {}` block, I need to decide whether to return the error as is (`return err`) or decorate it with further context (`return fmt.Errorf("stuff broke: %w", err)`). (Or use `%v` if I don't want to wrap. Yet another little nuance I find myself needing to explain to junior devs over and over. And don't get me started about putting that in the `fmt` package.)

So anyway, I've seen monstrosities of errors where there were 6+ "statement: statement: statement: statement: statement: final error" that felt like a dark comedy. I've also seen very high-level errors where I dearly wished for some intermediate context, but instead just had "failed to do a thing: EOF".

That all being said, stack traces are really expensive. So, you end up with some "fun" optimizations: https://stackoverflow.com/questions/58696093/when-does-jvm-s...

tsimionescu · 6 months ago
I'm not arguing that stack frames are as good as manually written error traces can be - they're clearly not. I am simply amazed that people include "generate error traces" as a form of "handling" an error.

The argument for explicit error values is often something like "it encourages people to actually handle their errors, rather than ignoring them". And on the face of it, this has some merit: we've all seen code that assumes an HTTP request can't fail, and now a small timeout crashes the entire backup procedure or whatever.

But if "handle the error" simply means "decorate it with a trace and return it", then exceptions already do this, then you're really admitting that there is no fundamental difference from a exception, because this is exactly what exceptions do, all on their own. Sure, they produce less useful traces, but that's usually a tiny difference. After all, the argument wasn't "you'll get better stack traces than exceptions give you", it was "people will be more careful to handle errors".

This is also relevant, because if the goal is to get better error traces, that can also be done with exceptions, with just some small improvements to syntax and semantics (e.g. add syntax for decorating a call site with user supplied context that will get included in any exception bubbled from it; add support in an exception to only print non-library stack frames, add support in the language to declare certain variables as "important" and have them auto-included in stack traces - many ideas).

jitl · 6 months ago
Whenever I get an error in NodeJS without a stack trace I am pretty pissed off about it. When my program is failing for some reason I really want to know where it came from, and the stack is hugely helpful in narrowing down the possibility space.
d3ckard · 7 months ago
From the Elixir's developer perspective, this is insane. The issue is solved in Erlang / Elixir by functions commonly returning {:ok, result} or {:error, description_or_struct} tuples. This, together with Elixir's `with` statement allows to group error handling at the bottom, which makes for much nicer readability.

Go could just add an equivalent of `with` clause, which would basically continue with functions as long as error is nil and have an error handling clause at the bottom.

tyre · 7 months ago
From all available evidence, there is no chance in hell Go could adopt a `with` statement.

Go is fascinating in how long it holds out on some of the most basic, obviously valuable constructs (generics, error handling, package management) because The Community cannot agree.

- Generics took 13 years from the open source release.

- 16 years in there isn’t error handling.

- Package management took about 9 years.

There’s value to deliberation and there’s value to shipping. My guess is that the people writing 900 GH comments would still write Go and be better off by the language having something vs. kicking the can down the road.

Ferret7446 · 6 months ago
We already have languages that ship features. Go is a lone lighthouse of stability in a sea of fancy languages. I'll play with your fancy languages, but I build my own projects that I actually use in Go because I can trust that it will keep working for a long time and if/when I need to go back to it to fix something in a couple of years I don't need to re-learn a bunch of crap that might have seeped through dependencies or the stdlib.
MeetingsBrowser · 6 months ago
> My guess is that the people writing 900 GH comments would still write Go and be better off by the language having something vs. kicking the can down the road.

My guess is they will still write Go even if error handling stays the same forever.

throwawa14223 · 7 months ago
Go's multiple return is in itself insane from my perspective. You cannot 'do' anything with a function that has multiple return types except assign them to a variable.
masklinn · 7 months ago
The saddest part is that Go's designers decided to use MRV but pretty much just as bad tuples: as far as I can tell the only thing Go uses MRV for which tuple wouldn't be better as is post-updating named return values, and you could probably just update those via the container.

Common Lisp actually does cool things (if a bit niche) things with MRVs, they're side-channels through which you can obtain additional information if you need it e.g. every common lisp rounding functions returns rounded value... and the remainder as an extra value.

So if you call

  (let ((v (round 5 2)))
    (format t "~D" v))
you get 2, but if you

  (multiple-value-bind (q r) (round 5 2)
    (format t "~D ~D" q r))
you get 2 and 1.

knutzui · 7 months ago
That's technically not true.

You can pass multiple return values of a function as parameters to another function if they fit the signature.

for example:

  func process[T any](value T, err error) {
    if err != nil {
      // handle error
    }
    // handle value
  }

this can be used in cases such as control loops, to centralize error handling for multiple separate functions, instead of writing out the error handling separately for each function.

  for {
    process(fetchFoo(ctx))
    process(fetchBar(ctx))
  }

9rx · 7 months ago
What else would you want to do with them? Maybe in rare cases you'd want to structure them into an array or something, but the inverse isn't possible either [e.g. func f(a, b, c int) -> f(<destructure array into arguments>)] so it isn't like it is inconsistent.

Perhaps what you are really trying to say is that multiple function arguments is insane full stop. You can pass in an array/tuple to the single input just the same. But pretty much every language has settled on them these days – so it would be utterly bizarre to not support them both in and out. We may not have known any better in the C days, but multiple input arguments with only one output argument is plain crazy in a modern language. You can't even write an identity function.

juped · 6 months ago
Haskellers and Rust fans think they own sum types, and people read their comments and blog posts and believe them, and decide they don't want sum types because they don't want to go down the horrifying Hindley-Milner rabbit hole.

But meanwhile it's just perfectly idiomatic Erlang and Elixir, none of that baggage required. (In fact, the sum types are vastly more powerful than in the ML lineage - they're open.)

Deleted Comment