[HN Gopher] (On | No) Syntactic Support for Error Handling
___________________________________________________________________
(On | No) Syntactic Support for Error Handling
Author : henrikhorluck
Score : 269 points
Date : 2025-06-03 16:18 UTC (6 hours ago)
(HTM) web link (go.dev)
(TXT) w3m dump (go.dev)
| codr7 wrote:
| I just wish the could stabilize the compiler before piling on
| more features.
|
| I regularly run into internal compiler errors these days for
| pretty normal looking code.
|
| It's getting to the point where I'm reluctant to invest more time
| in the language right now.
|
| UPDATE: See comment below for full error message and a link to
| the code.
| johnfn wrote:
| In that case, you might enjoy this part of the article:
|
| > For the foreseeable future, the Go team will stop pursuing
| syntactic language changes for error handling
| Thaxll wrote:
| I've never seen a single internal compiler issue in 10 years of
| working in Go.
|
| What error are you talking about?
| kbolino wrote:
| Not OP, but I found a compiler bug in the initial release of
| 1.18, though it was also quickly fixed:
| https://github.com/golang/go/issues/51733
| codr7 wrote:
| Me neither until the latest version.
|
| Internal compiler error, type2 something, unknown line
| number.
|
| I'll have to look it up.
|
| Downvotes for reporting an error, very classy HN.
|
| UPDATE: See comment below for full error message and a link
| to the code.
| shivamacharya wrote:
| If you do stuff the language doesn't support, it's not the
| languages problem.
| codr7 wrote:
| I did nothing weird, played around a bit with the new
| iterators.
|
| Internal compiler errors are very much the implementation's
| problem.
|
| UPDATE: See comment below for full error message and a link
| to the code.
| shivamacharya wrote:
| I only say this because I have yet to encounter a single
| internal compiler error in years of writing Go
| professionally. I can't conceive of the kind of code one
| must be writing for internal compiler errors to be a
| repeated issue.
| codr7 wrote:
| So here's the complete error message:
|
| <unknown line number>: internal compiler error:
| unexpected types2.Invalid
|
| Please file a bug report including a short program that
| triggers the error. https://go.dev/issue/new
|
| And here's the code that triggers it:
|
| https://github.com/codr7/shi-go/blob/main/src/shi/call.go
|
| The code is never referenced in the project, but running
| make in the project root with this file in it triggers
| the error. Remove the file and the error disappears.
|
| Happy now?
| kiitos wrote:
| Paste exactly what commands you run and exactly their
| output, including exactly the rev of the repo you're
| running them in.
|
| This code is buggy from tip to tail, my goodness!
| Starting with Make targets like `go test src/tests/*`
| (invalid syntax), no `gofmt` formatting, everything in
| https://github.com/codr7/shi-go/blob/main/src/shi/vm.go,
| no tests anywhere -- really looks like you're programming
| against a language spec that you've invented yourself,
| maybe influenced by Go, but certainly not valid Go as
| defined!
|
| > Remove the file and the error disappears
|
| Remove the file and the code no longer compiles, because
| it contain definitions that are used by other code in the
| package.
| stefanos82 wrote:
| I have hard time understanding why they didn't go with
| func printSum(a, b string) error { x :=
| strconv.Atoi(a) or { return error }
| y := strconv.Atoi(b) or { return error
| } fmt.Println("result:", x + y) return
| nil }
|
| or something along these lines...
| wredcoll wrote:
| Oh cool, you've reinvented perl.
| righthand wrote:
| > Unfortunately, as with the other error handling ideas, this
| new proposal was also quickly overrun with comments and many
| suggestions for minor tweaks, often based on individual
| preferences. Ian closed the proposal and moved the content into
| a discussion to facilitate the conversation and to collect
| further feedback. A slightly modified version was received a
| bit more positively but broad support remained elusive.
|
| > 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.
| shivamacharya wrote:
| If you do this, returning the error last is now part of the
| language rather than a convention. You'd be making a pretty
| large language change.. and for what? One line of code that's
| already folded away by any modern editor?
| alanbernstein wrote:
| Seems like it's only worth the cost of the change if it removes
| all three verbose lines per check, this only removes one.
| arp242 wrote:
| You need to assign the error to a variable, probably. So it
| would have to be more something like: n :=
| strconv.Atoi(s) or |err| { return fmt.Errorf("foo:
| %w", err) } n := strconv.Atoi(s) or |err| {
| if !errors.Is(err, pkg.ErrFoo) return
| fmt.Errorf("foo: %w", err) } }
|
| Just "error" (which shadows the built-in type) won't really
| work.
|
| I'm just making up syntax here to illustrate the point; doesn't
| look too brilliant to me. A func might be a bit more "Go-like":
| n := strconv.Atoi(s) or func(n int, err error) {
| return fmt.Errorf("foo: %w", err) }
|
| All of this is kind of a moot point at Robert's blog post says
| that these proposals won't be considered for the foreseeable
| future, but IMHO any error handling proposal should continue to
| treat errors as values, which means you should be able to use
| fmt.Errorf(), errors.Is(), mylogger.Error(), etc.
| 827a wrote:
| IMO: The position of "we aren't sure what the right approach to
| improvement is so we aren't going to do anything" has killed far
| bigger and more important projects, companies, and even
| countries, than Golang. Adapt or die; the world isn't going to
| wait for you.
|
| (I love the Go team, and appreciate everything they do. I'm just
| sad to see a language I used to love fail to keep pace with many
| of the other options out there today.)
| justin66 wrote:
| Go adoption has only increased over time.
| 827a wrote:
| There's no methodologically sound way to measure its
| popularity available to most people to make an argument on
| its popularity one way or the other. That's why I didn't, and
| that's why you shouldn't either.
| justin66 wrote:
| If you say so, but the concern you floated (The position of
| "we aren't sure what the right approach to improvement is
| so we aren't going to do anything" has killed far bigger
| and more important projects, companies, and even countries,
| than Golang. ) is fundamentally silly, given how long Go
| has been in business and seemingly thriving while applying
| this approach to a few select, controversial features.
|
| If it was going to be killed by this approach, it would now
| be dead.
| MeetingsBrowser wrote:
| Changing too quickly is a much bigger problem. It may not be
| ideal, but I think leaning towards being slow makes sense in
| this context.
|
| Every person/company using Go chose to use it knowing how
| errors are handled.
|
| Each new way of error handling seems to upset a large number of
| users, some of which may not have chosen Go had the newer
| system been in place originally.
|
| If it is impossible to know which choice is correct, at least
| everyone has some baseline level of acceptance for the status
| quo.
| 827a wrote:
| Changing too quickly is not a problem. Changing too quickly
| can _lead_ to problems.
|
| I don't agree that the problems it leads to are bigger
| problems than stagnation. I also don't believe they're
| smaller problems; sorting the problems by size is
| intractable, as it is situation dependent.
|
| The challenge is in the definition of "too quickly"; if
| fifteen years of stagnation in addressing more productive
| error handling is the "right pace" of innovation, or lack-
| there-of; is twenty years? Thirty years? One hundred years?
| How do you decide when the time is right? Is the Go team just
| waiting out the tides of the Vox Populi, and maybe one day a
| unified opinion from the masses will coalesce?
|
| That's weak.
| acedTrex wrote:
| In this particular case the argument seems to be that there is
| not even consensus improvement is NEEDED. If you cant even
| agree on that then how do ever hope to agree to a change in the
| general sense.
| arp242 wrote:
| > Adapt or die
|
| It's not my impression Go is dying. Seems rather overblown.
|
| And this "but $other_lang has it! You must have it! Adapt or
| die!" type of reasoning is how you end up with C++.
| 827a wrote:
| I never said Go is dying. I said go must adapt, or it will
| die. That's future-tense.
|
| Sure, you can end up with C++ (which is still by some
| measures the most popular programming language in the world,
| so that's not a bad place to be). You can also end up with
| Rust, or Kotlin, or any one of the _literally every other
| programming languages in any ranking 's Top 30_, all of which
| have more ergonomic error handling.
|
| A better example in the opposite direction is Java: Its a
| language that spent years refusing to adapt to the ever-
| changing needs of software engineers. Its legacy now. That is
| not Go's present, but it is Go's future at its current pace.
| Still powering a ton of projects, but never talked about
| except in disdain like "ugh that Go service is such tech
| debt, can we get time modernize it next sprint". I don't want
| that for the language.
| righthand wrote:
| Hopefully this is the final nail in the coffin for all the "but I
| have to handle my errors" whining.
| aystatic wrote:
| I don't know that there's whining about "having to handle
| errors" in principle, it's pretty clearly a complaint with the
| syntax and semantics of doing so
|
| Some languages even make omitting error handling impossible!
| (e.g. Result sum types). None have anywhere near the amount of
| "whining" Go seems to attract
| maxwellg wrote:
| 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 wrote:
| 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 wrote:
| Even PHP has better error handling with the levels, and the @
| (at operator) to suppress errors at a callsite.
|
| Even bash has -e :)
| bravesoul2 wrote:
| 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.
| marcoc wrote:
| Now that I think about it, are there any color schemes or
| extensions that highlight the error handling logic differently so
| that one can better focus on the "main" logic flow of the code
| while the error handling logic is still there?
| Refreeze5224 wrote:
| I would love something like this, and if it exists, I've not
| come across it. Offloading a way of differentiating error
| handling syntax vs. normal code to the IDE seems like a nice
| way of handling this issue.
| renewiltord wrote:
| Goland folds it visually partially
| https://github.com/golang/vscode-go/issues/2311
| rollcat wrote:
| You would have to infer the intent of the code. One example,
| from a small project I'm working on: $ find .
| -name "*.go" | xargs grep 'if err !=' | wc -l 242
| $ find . -name "*.go" | xargs grep 'if err ==' | wc -l
| 12
|
| So about 5% of the error checking code is about handling the
| edge cases, where we are very much interested in what the error
| actually is, and need to handle those conditions carefully.
|
| If you discard that as "error handling noise", you're in for a
| bug. Which is, by the way, perhaps the worst side-effect of
| verbose, repetitive error handling.
|
| Apropos syntax highlighting: many themes in regular use
| (certainly most of the defaults) choose a low-contrast color
| for the comments. The comments are often the most important
| part of the code.
| Refreeze5224 wrote:
| I don't really understand this decision. They know from developer
| surveys that verbose and repetitive error handling is literally
| the number 1 issue. Seeking the perfection of syntax that
| everyone agrees on seems to be the enemy of providing _some_ good
| alternative to the status quo.
|
| Their comment about providing some new syntax and people being
| forced to use it seems off base to me. It's nice to not have
| multiple ways of doing things, but having only 2 when it comes to
| error handling does not seem like a big deal. I imagine people
| will just use their preference, and a large percentage of people
| will have a less verbose option if they want it.
| munificent wrote:
| _> They know from developer surveys that verbose and repetitive
| error handling is literally the number 1 issue._
|
| Agreement on a problem does not imply agreement on a solution.
|
| It's not about perfection. It's about not having a solution
| that gets anywhere near a majority approval.
|
| Let's say your neighborhood has an empty plot of land owned by
| the city that is currently a pile of broken chunks of concrete,
| trash, and tangled wire. It's easy to imagine that there is
| unanimous agreement by everyone in the neighborhood that
| something better should be placed there.
|
| But the parents want a playground, the pet owners want a dog
| park, the homeless advocates want a shelter, the nature lovers
| want a forest, etc. None of them will agree to spend their tax
| dollars on a solution that is useless to them, so no solution
| wins even though they all want the problem solved.
| Grikbdl wrote:
| Even if people in your example couldn't agree on a particular
| alternative, the outcome still is a less attractive area,
| maybe some will move out and fewer people move in. So, _any_
| solution would be better than the status quo - and they all
| would probably agree on that.
|
| The lack of a good error handling story to a lot of people
| puts go in a mental trash bin of sorts. Similar (but
| different) reasons eg Java goes to a mental trash bin. I
| think leaving this issue unhandled will only make go look
| worse and worse in comparisons as the programming language
| landscape evolves. It might take 10 or 20 years but it'll
| always be unique in having "trash bin worthy" error handling.
| (this can perhaps be argued - maybe exceptions are worse, but
| at least they're standard).
| munificent wrote:
| _> So, any solution would be better than the status quo -
| and they all would probably agree on that._
|
| The point is that people do _not_ agree that _any_ solution
| is better than the status quo. In my analogy, if
| redeveloping that plot of land is quite expensive in tax
| dollars, people will prefer it be left alone completely so
| that money can be spent elsewhere than have it squandered
| on a "solution" that does nothing for them.
|
| Likewise in Go, adding language features has a quite large
| cost in terms of cognitive load, decision load,
| implementation cost, etc. After many many surveys and
| discussions, it's clear that there is no consensus among
| the Go ecosystem that _any_ error handling strategy is
| worth that cost.
| throwaway894345 wrote:
| In the analogy we might suppose everyone agrees that there
| is a problem and any solution is better than the status
| quo, but that's extremely unlikely in the case of Go. In my
| experience discussing this issue with Go users and critics,
| a lot of Go users find the status quo to be minimally
| chafing.
|
| > The lack of a good error handling story to a lot of
| people puts go in a mental trash bin of sorts. ... It might
| take 10 or 20 years but it'll always be unique in having
| "trash bin worthy" error handling. (this can perhaps be
| argued - maybe exceptions are worse, but at least they're
| standard).
|
| Remember that the context is _syntactic_ error handling
| proposals, not proposals for error handling generally--the
| maintainers are saying they 're only going to close syntax-
| only error handling proposals. While I have no doubt that
| there are lots of people who write of Go for its error
| handling syntax alone, I don't see any reason why a
| language community should prioritize the opinions of this
| group.
|
| Additionally, while I have plenty of criticism for Go's
| error handling, I can't take "Go's error handling is 'trash
| bin worthy'" seriously. There are no languages that do
| error handling well (by which I mean, no implicit control
| flow and one obvious way to create errors with appropriate
| error context, no redundant context, clear error messages,
| etc). Go and Rust probably both give you the tools
| necessary to do error handling well, but there's no
| standard solution so you will have issues integrating
| different libraries (for example, different libraries will
| take different approaches to attaching error context, some
| might include stack traces and others won't, etc). It's a
| mess across the board, and verbosity is the least of my
| problems.
| pixl97 wrote:
| >It's about not having a solution that gets anywhere near a
| majority approval.
|
| You'll never get it in any non-gamed environment.
|
| In democratic voting in FPtP systems if there isn't a
| majority winner you'll take the top two and go to runoffs
| forcing those that are voting to pick the best of the bad
| choices.
|
| This is the same thing that will typically happen in the city
| you're talking about, hence why most democracies are
| representative and not direct.
| arp242 wrote:
| > They know from developer surveys that verbose and repetitive
| error handling is literally the number 1 issue.
|
| According to 13% of respondents. So yes, it's the "#1 issue",
| but also not by a huge overwhelming majority or anything.
| pixl97 wrote:
| Honestly this is only because they made a bad survey. A
| ranked choice is better.
|
| Lets say you have 5 choices. You give each choice a voting
| weight of 1 (not an issue) to (5 biggest issue). You only get
| to pick a weight once.
|
| So in this type of voting even if everybody put error
| handling and #4 it could still win by a large margin if the 5
| values were spread out over other concerns.
| ignoramous wrote:
| > _I don 't really understand this decision._
|
| The decision is disappointing, but understandable.
|
| The blog post attempted to explain it, but it comes down to: A
| lot of energy has been expended without the community _and_ the
| core team reaching any form of consensus. The current error
| handling mechanism has entrenched itself as idiomatic for a
| very long time now. And since the promising ones among the
| various proposals involve language changes, the core team,
| which is stretched already, isn 't willing to commit to it _at
| this time_ , especially given the impact.
| nimish wrote:
| This paragraph alone is fundamentally better than the page or
| so of text in the blog post.
|
| I'm not sure what it is about the style of technical writing
| I've seen lately but just directly getting to the point
| versus trying to obfuscate the thesis on a potentially
| controversial topic is increasingly rare
| shadowgovt wrote:
| Python is a bit of a counter-example these days. I think
| they're in a good place right now, but it's hard to argue
| they've stuck to the premise of "There should be one-- and
| preferably only one --obvious way to do it."
|
| - I need to do string interpolation: am I using f-strings or
| `string.format` or the modulo operator?
|
| - I need to do something in a loop. Well, I can do that. But I
| could also just do a list or sequence comprehension... Or I
| could get fancy and combine the two!
|
| And such and so-on, but these are the top examples.
|
| Changing these things _strictly adds_ cognitive load because
| you will eventually encounter libraries that use the other
| pattern if you 're familiar with the one pattern. And at the
| limit of what this can do to a language, you get C++, where the
| spec exceeds the length of the Bible and many bits of it create
| undefined behavior when used together.
|
| I think Go's project owners are very justifiably paranoid about
| the consequences of opening the Pandora's box on new features,
| even if it means users have to deal with some line-noise in the
| code.
| yandrypozo wrote:
| people that answer surveys don't represent all Go developers,
| many of us are fine with the error status quo
| hathawsh wrote:
| What I wonder about is the pool of _potential_ Go developers.
| Is the error handling issue serious enough to stop developers
| from even considering Go? Go would have been an obviously
| better choice than most languages 30 years ago, but today
| there are many more good options.
| tuckerman wrote:
| If you shake things up so much that users who previous
| dismissed your language are interested, you might also be
| making a big enough change that your current users look
| around as well. The pool of prospective new language users
| is always large but they won't join a language that is
| dying because it churned all its existing users and package
| maintainers.
|
| I say this as someone that gets a very bad taste in my
| mouth when handling errors in go but use it a fair bit
| nonetheless.
| homebrewer wrote:
| If you're writing the universe, maybe. There aren't that
| many competitors when you take the ecosystem into
| consideration. It is the only reason I tolerate Go where it
| makes (some) sense -- mostly CLI utilities that are too
| complicated for bash.
| arp242 wrote:
| Every language has the _potential_ to attract new
| developers if they change /add just this one thing.
| throwaway894345 wrote:
| > Is the error handling issue serious enough to stop
| developers from even considering Go
|
| If it is, then I suspect those developers are going to have
| a thousand other non-overlapping reasons not to consider
| Go. It seems like a colossal waste of time to court these
| people compared with optimizing Go for the folks who
| already are using it or who reasonably might actually use
| it (for example, people who would really like to use Go,
| but it doesn't support their platform, or it doesn't meet
| some compliance criteria, or etc).
| pixl97 wrote:
| Ah, voting by survivorship bias.
| throwaway894345 wrote:
| No, this is not an example of survivorship bias. Here's
| the wiki link for your reference.
| https://en.wikipedia.org/wiki/Survivorship_bias
| pixl97 wrote:
| No this is an example of survivorship bias.
|
| Let's say Go has such bad error handling that it becomes
| the number one reason people don't use it.
|
| The people left that do use it will be the ones that
| don't care about error handling. Hence you're asking the
| people that don't care versus 90% of the audience you've
| already lost.
| teeray wrote:
| > ...having only 2 when it comes to error handling does not
| seem like a big deal. I imagine people will just use their
| preference...
|
| I foresee endless PR arguments about whether err != nil is the
| best practice or whatever alternative exists. Back-and-forth
| based on preference, PRs getting blocked since someone is using
| the "other" style, etc. Finally the org is tired of all this
| arguing and demands everyone settle on the one true style
| (which doesn't exist), and forces everyone to use that style.
| That is where the "forced to use it" comes from.
|
| From the early days, Go has taken principled stands on matters
| like this, striving for simplicity and one way to do something.
| For example, `go fmt` cut through all the tabs vs. space
| nonsense by edict. "Gofmt's style is no one's favorite, yet
| gofmt is everyone's favorite."
| nepthar wrote:
| Yeah, this is the single biggest reason I avoid go - I just
| don't want to clutter my "happy path" logic. It makes things
| harder to reason about.
|
| "Errors are values", sure. Numbers are values and Lists are
| values. I use them differently, though.
|
| I wonder if there could be "stupid" preprocessing step where I
| could unclutter go code, where you'd make a new token like
| "?=", that got replaced before compile time. For instance, "x
| ?= function.call();" would expand to "x, err :=
| function.call(); if (err != nil) return err;"
| skydhash wrote:
| There's no happy path in programming. Errors are just some of
| the many states in the code and the transition to them
| doesn't disappear magically because you chose to not specify
| them. Actually returning an error is just a choice, you can
| chose to handle the situation, maybe log the error and go on
| your merry way. Or you panic() and cancel the whole call
| stack back to a recover(). I like Go because it forces you to
| be explicit.
| jiehong wrote:
| The language was first designed without consensus and then
| released. It was used anyways.
|
| But now a sort of democracy is required for changes. I'm not
| sure this is necessary.
| wrs wrote:
| The module system shows the Go core team will make unilateral
| changes if they feel like it. But if I read this correctly,
| there's not a consensus even amongst the Go team on what to
| do here.
| throwaway894345 wrote:
| According to TFA, it's not quite "if they feel like it",
| it's when there isn't consensus among the community but (1)
| it's clear that there is a problem and (2) the Go
| architects can agree on a path forward.
| rco8786 wrote:
| This feels like it's making it worse lol
| rollcat wrote:
| Snip: if err != nil { return
| fmt.Errorf("invalid integer: %q", a) }
|
| Snip. No syntax for error handling is OK with me. Spelling out
| "hey I actually do need a stack trace" at every call site isn't.
| skydhash wrote:
| You actually don't. In mistakes #52 "Handling an error twice"
| from the book "100 Go Mistakes and How to Avoid Them" by Teiva
| Harsanyi, the recommendation is to either return an error to
| the caller or handle the situation (and maybe logging it).
| Sometimes you wnat some extra information as to why it's an
| error, so you wrap it.
|
| Go forces you to be explicit about error handling. Java syntax
| is not that much better. JavaScript, Kotlin, Swift,... is more
| about avoiding null pointer exception than proper error
| handling.
| rollcat wrote:
| I return the error to the caller. The caller returns it to
| their caller. 5 frames up, _someone_ gets a [syscall.EINVAL],
| and _has_ to figure out what to do about it. Perhaps it 's
| time to log it?
|
| If I had to write my own "100 mistakes" book, "assuming the
| callee knows what to do" would be somewhere in the top 20,
| down below "I won't need to debug this".
| skydhash wrote:
| It's all about designing software. The callee is the one
| encountering the error, not any of the caller up in the
| stack trace. Somewhere in the call chain, there's a need to
| take a decision.
|
| So you, as the developer, decide where that needs to be. It
| may be at the callee level (like an exponential retry) or
| at the caller level (display an error message). In the
| later case, you may want to add more information to the
| error data block, so that the caller my handle the
| situation appropriately. So if you want tracing, you just
| need to wrap the error and returns it. Then your logging
| code have all the information it needs: like
| [error saving file [permission error [can't access file]]]
|
| instead of just [syscall.EINVAL].
| rollcat wrote:
| > So if you want tracing, you just need to wrap the error
| and returns it.
|
| Here we go, fifth time we're both spelling this one out.
| This thread is now a meta-self-joke.
| zb3 wrote:
| Thankfully repetitive language is less of a problem now that we
| have AI. The current syntax is just "add error handling to this
| function" :)
| Jtsummers wrote:
| You're getting downvoted, but this was what tptacek basically
| wrote about. Key points from his blog are that LLMs are good at
| tedium, and Go's structure is highly repetitive and amenable to
| LLM generation. The error handling tedium is probably part of
| why it's highly repetitive.
|
| > I work mostly in Go. I'm confident the designers of the Go
| programming language didn't set out to produce the most LLM-
| legible language in the industry. They succeeded nonetheless Go
| has just enough type safety, an extensive standard library, and
| a culture that prizes (often repetitive) idiom. LLMs kick ass
| generating it.
|
| https://news.ycombinator.com/item?id=44163063 - 2386 comments
| homebrewer wrote:
| Writing it was never the problem if you're using proper tools,
| e.g. an actual IDE (IDEA does fine), or at least a snippet
| manager for your text editor. Inserting a wrapping error
| handling snippet requires two key presses.
|
| It's reviewing mountains of that crap that's the problem,
| especially if there are non-trivial cases hidden in there, like
| returning the error when `err == nil` (mentioned by others in
| this thread).
| whstl wrote:
| I used to hate the repetitive nature of Go's error handling until
| I was burned by bad/mysterious error messages in production.
|
| Now my error handling is not repetitive anymore. I am in peace
| with Golang.
|
| However I 100% get the complaint from the people who don't need
| detailed error messages.
| arp242 wrote:
| > Of course, there are also valid arguments in favor of change:
| Lack of better error handling support remains the top complaint
| in our user surveys.
|
| Looking at that survey, only 13% mentioned error handling. So
| that means 87% _didn 't_ mention it. So in that sense, perhaps
| not too much weight should be given to that?
|
| I agree the verbosity fades into the background, but also feel
| _something_ better can be done, somehow. As mentioned there 's
| been a gazillion proposals, and some of them seem quite
| reasonable. This is something where the original Go design of "we
| only put in Go what Robert, Ken, and Rob can all agree on" would
| IMHO be better, because these type of discussions don't really
| get a whole lot better with hundreds of people from the interwebz
| involved. That said, I wasn't a fan of the try proposal and I'm
| happy it didn't make it in the language.
|
| And to be honest in my daily Go programming, it's not that big of
| a deal. So it's okay.
| smw wrote:
| "biggest challenge", it's not multiple choice
| throwaway71271 wrote:
| Everything is fine
|
| I dream if err, if err dreams me.
| pie_flavor wrote:
| 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.
| hu3 wrote:
| > 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 wrote:
| 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 wrote:
| 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"
| arccy wrote:
| and this is how rust gains its reputation as an ugly to read
| language with inconsistent syntax: design by committee
| wtetzner wrote:
| Ugly is subjective, but which part of the syntax is
| inconsistent?
| arccy wrote:
| .await something that looks like a field access but
| actually does something else
| dlisboa wrote:
| I don't know if this qualifies as inconsistent, but:
|
| `impl T for for<'a> fn(&'a u8) {}`
|
| The `for` word here is used in two different meanings, both
| different from each other and from the third and more usual
| `for` loop.
|
| Rust just has very weird syntax decisions. All
| understandable in isolation but when put altogether it does
| yield a hard to read language.
| steveklabnik wrote:
| All three have the same underlying idea: do this for
| every thing of that. In the first case, it's implement a
| trait for a type. In the second case, it's "for all
| choices of the lifetime" and for a for loop, it's do
| something for each element of a collection.
| dlisboa wrote:
| I understand how that seems logical in isolation but it's
| just not how syntax is usually read by people. It's done
| so as part of a reading context instead of as separate
| syntatical tokens. The underlying idea is not the same
| for the reader because the context is vastly different.
| wtetzner wrote:
| This feels disingenuous. I have a hard time imagining a
| case where someone would find this confusing.
| steveklabnik wrote:
| Sure, and I think that's insightful: what you may
| consider a mess, I may consider orthogonal!
| NobodyNada wrote:
| Rust tends to _prefer_ reusing keywords (in unambiguously
| different contexts) rather than introducing new ones,
| because adding a new keyword is a backwards compatibility
| break that requires a new language edition and creates
| migration pain for anyone who had an identifier with the
| same name as the new keyword.
| steveklabnik wrote:
| While that's true, all three of these uses pre-date Rust
| 1.0, so there was total freedom in this case.
| j-krieger wrote:
| I tended to disagree on this discussion in the past, but I
| increasingly no longer do. For example, let's have a look
| at the new `implicit lifetime capturing` syntax:
| fn f(x: &()) -> impl Sized + use<'_> { x }
|
| It's weird. It's full of sigils. It's not what the Rust
| team envisioned before a few key members left.
| jeremyjh wrote:
| Whereas Go has taken the approach of designing everything
| "later".
| mseepgood wrote:
| It's good to have languages with different approaches to
| design and and with different design philosophies.
| zaptheimpaler wrote:
| 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.
| codedokode wrote:
| 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.
| arccy wrote:
| you don't Go is explicit about things.
|
| maybe caps for export is ugly, it's not much different from
| how python hides with _
| callc wrote:
| I find both the golang uppercase lowercase naming scheme
| and python underscores for private (and __ for *extra*
| private) to be terrible design choices.
|
| They are hidden functionality, a set of rules which must
| be remembered. "Make sure to do <weird trick> because
| that mean <X> in <PL>"
|
| Leave identifier names alone. Packing extra info inside
| is unnecessary mental burden
| joaohaas wrote:
| If you don't care about field access just always write
| fields with uppercase. Any APIs you're using only expose
| uppercased variables as well, so it'll stay consistent.
|
| The public/private stuff is mostly useful for publishing
| modules with sound APIs.
| olalonde wrote:
| 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.
| Thaxll wrote:
| "despite multiple complete perfect proposals on how to do it"
|
| There is no such a thing.
| GoatInGrey wrote:
| > 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 wrote:
| 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.
| tialaramex wrote:
| Rust's try! macro was+ "essentially a macro for this
| entirely boilerplate operation" but the Try operator ? is
| something more interesting because in the process they
| reified ControlFlow.
|
| Because implementing Try for your own custom types is
| unstable today if you want to participate you'd most likely
| provide a ControlFlow yourself. But in doing that you're
| making plain the distinction between success/ failure and
| early termination/ continuing.
|
| + Technically still is, Rust's standard library macros are
| subject to the same policies as the rest of the stdlib and
| so try! is marked deprecated but won't be removed.
| kiitos wrote:
| It's just simply not the cause that error handling is an
| "entirely boilerplate operation", nor that any kind of
| macro in Go "would be less terrible" than the status quo,
| nor is it true that decisions that Rust made are even
| applicable to Go. Believe it or not, the current approach
| to error handling actually does work and actually is better
| than most/all proposals thru the lens of Go's original
| design intent.
| kiitos wrote:
| 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.
| bravesoul2 wrote:
| 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.
| henry700 wrote:
| >For a while, the lack of generics surpassed complaints about
| error handling, but now that Go supports generics, error handling
| is back on top
|
| >The Go team takes community feedback seriously
|
| It feels like reading satire, but it's real.
| danenania wrote:
| They clearly are wrestling with these issues, which to me seems
| like taking the feedback seriously. Taking feedback seriously
| doesn't imply you have to say yes to every request. That just
| gives you a design-by-committee junk drawer language. We
| already have enough of those, so personally I'm glad the Go
| team sticks to its guns and says no most of the time.
| ummonk wrote:
| How is Go not a design-by-committee language? They don't have
| a single lead language developer or benevolent dictator, and
| as this blog demonstrates, they're very much driven by
| consensus.
| danenania wrote:
| True, but they're very picky as committees go. But yeah,
| maybe not the best use of that expression...
| JamesSwift wrote:
| I havent followed this argument closely so forgive me if I'm
| missing relevant discussion, but I dont see why the Rust style
| isnt just adopted. Its the thing I immediately add now that I
| have generics in Go.
|
| I only see this blurb in a linked article:
|
| > But Rust has no equivalent of handle: the convenience of the ?
| operator comes with the likely omission of proper handling.
|
| But I fail to see how having convenience equates to ignoring the
| error. Thats basically half of my problem with Go's approach,
| that nothing enforces anything about the result and only
| minimally enforces checking the error. eg this results in
| 'declared and not used: err' x, err :=
| strconv.Atoi("123") fmt.Println("result:", x)
|
| but this runs just fine (and you will have no idea because of the
| default 0 value for `y`): x, err :=
| strconv.Atoi("123") if err != nil { panic(err)
| } y, err := strconv.Atoi("1234")
| fmt.Println("result:", x, y)
|
| this also compiles and runs just fine but again you would have no
| idea something was wrong x, err :=
| strconv.Atoi("123") if err != nil { }
| fmt.Println("result:", x)
|
| Making the return be `result` _enforces_ that you have to make a
| decision. Who cares if someone yolos a `!` or conveniently uses
| `?` but doesnt handle the error case. Are you going to forbid
| `panic` too?
| umanwizard wrote:
| Go can't have Result because they don't have sum types, and
| they can't add them because of their bizarre insistence that
| every type has to have a designated zero value.
| masklinn wrote:
| > they can't add them because of their bizarre insistence
| that every type has to have a designated zero value.
|
| Nothing prevents adding union types with a zero value. Sure
| it sucks, but so do universal zero values in pretty much
| every other situation so that's not really a change.
| umanwizard wrote:
| Making it so all sum types have to be nillable would make
| them dramatically worse (the basic motivating example for
| sum types is Option, the whole point of which is to get rid
| of NULL). I guess this is in agreement with your point.
| masklinn wrote:
| > the basic motivating example for sum types is Option,
| the whole point of which is to get rid of NULL
|
| I don't think that's the case in Go: whereas I got the
| impression the C# team started souring on default() after
| generics landed (possibly because nullable value types
| landed alongside and they found out that worked just fine
| and there was no reason nullable reference types
| wouldn't) I don't really get that impression from the Go
| team, even less so from them still mostly being Googlers
| (proto3 removed both required fields and explicit default
| values).
| 9rx wrote:
| _> I dont see why the Rust style isnt just adopted._
|
| Mostly because it is not entirely clear what the Rust-style
| equivalent in Go might be. What would Rust's "From" look like,
| for example?
| JamesSwift wrote:
| Sorry, I wasnt specific in that part. When I say 'rust style'
| Im really just referring to a union type of `result | error`,
| with a way to check the state (eg isError and isResult) along
| with a way to get the state (eg getResult and getError).
| Optionally '?' and '!' as sugar.
|
| That said, the other responder points out why the sum type
| approach is not favored (which is news to me, since like I
| said I havent followed the discussion)
| 9rx wrote:
| In Go, values are to be always useful, so `result | error`
| would be logically incorrect. `(result, result | error)`,
| perhaps - assuming Go had sum types, but that's a bit
| strange to pass the result twice.
|
| Just more of the pitfalls of it not being clear how Rust-
| style applies to an entirely different language with an
| entirely different view of the world.
| JamesSwift wrote:
| What is useful about the value of `x` in the following
| code? x, err := strconv.Atoi("this is
| invalid")
|
| On the contrary, `x` is _lying_ to you about being useful
| and you have absolutely no idea if the string was "0" or
| "not zero"
| 9rx wrote:
| _> and you have absolutely no idea if the string was "0"
| or "not zero"_
|
| You do - err will tell you. But in practice, how often do
| you really care?
|
| As Go prescribes "Make the zero value useful" your code
| will be written in such a way that "0" is what you'll end
| up using downstream anyway, so _most_ of the time it
| makes no difference. When it does, err is there to use.
|
| That might not make sense in other languages, but you
| must remember that they are other languages that see the
| world differently. Languages are about more than syntax -
| they encompass a whole way of thinking about programs.
| kbolino wrote:
| I think it's worth noting that, while the general
| consensus has converged around (T, error) meaning T XOR
| error, it does not _necessarily_ mean that. There are
| some places that violate this assumption, like the
| io.Reader and io.Writer interfaces. Especially io.Reader,
| where you can have (n >0, io.EOF), which also isn't even
| a proper error condition! (This isn't a big problem,
| though, since you rarely need to directly call Read or
| Write).
| kiitos wrote:
| If a function `func foo() (int, error)` returns a non-nil
| error, then the corresponding `int` is absolutely invalid
| and should never be evaluated by the caller, unless docs
| explicitly say otherwise.
| XorNot wrote:
| And this is why I prefer exceptions.
|
| Errors are common but they _are_ errors: they absolutely
| represent an exceptional branch of your control flow
| every time.
|
| It seems reasonable to ask if that int should even be
| available in the control flow syntactically.
| hobs wrote:
| To be fair Rust doesn't have sum type it has enums, which I
| feel like you could do in Go, but I haven't read the
| arguments.
| 9rx wrote:
| Technically, Rust has sum types (a.k.a. tagged unions)
| that use an enum to generate the tag. So, while
| enumeration is involved, sum types is still a better
| description of what it is.
| hmry wrote:
| Enum is what they call it (perhaps to appear more
| familiar to C++ programmers?), but from a computer
| science standpoint they are classic sum types.
| kbolino wrote:
| It's an interesting idea. Right now, you can do something
| like this: res := someFunc() // func()
| any switch v := res.(type) { case error:
| // handle error case T: // handle
| result default: panic("unexpected
| type!") }
|
| Then, presumably, a T|error sum type would be a
| specialization of the any type that would allow you to
| safely eliminate the default arm of the switch statement
| (or so I would like to think -- but the zero value issue
| rears its ugly head here too). Personally, I'd also like to
| see a refinement of type switches, to allow different
| variable names for each arm, resulting in something like
| the following hypothetical syntax: switch
| someFunc().(type) { case err := error:
| // handle error case res := T: //
| handle result }
|
| However, there's no real syntactic benefit _for error
| handling_ to be found here. I like it (I want discriminated
| unions too), but it 's really tangential to the problem.
| I'd honestly prefer it more for other purposes than errors.
| masklinn wrote:
| > What would Rust's "From" look like, for example?
|
| Idiomatic Go type-erases error types into `error`, when there
| is even a known type in the first place.
|
| Thus `From` is not a consideration, because the only `From`
| you need is impl<'a, E> From<E> for Box<dyn
| Error + 'a> where E: Error + 'a,
|
| and that means you can just build that in _and nothing else_
| (and it 's really already built-in by the implicit upcasting
| of values into interfaces).
| abtinf wrote:
| > Its the thing I immediately add now that I have generics in
| Go.
|
| If you're willing to share, I'm very curious to see a code
| example of what you mean by this.
| JamesSwift wrote:
| This is from a while back but was the first thing I thought
| of: https://gist.github.com/J-Swift/96cde097cc324de1f8e899ba3
| 0a1...
|
| I ripped most of it off of someone else, link in the gist
| masklinn wrote:
| > But I fail to see how having convenience equates to ignoring
| the error.
|
| The convenience of writing `?` means nobody will bother
| wrapping errors anymore. Is what I understand of this extremely
| dubious argument.
|
| Since you could just design your `?` to encourage wrapping
| instead.
| NobodyNada wrote:
| > Since you could just design your `?` to encourage wrapping
| instead.
|
| Which is exactly what Rust does -- if the error returned by
| the function does not match the error type of `?` expression,
| but the error can be converted using the `From` trait, then
| the conversion is automatically performed. You can write out
| the conversion implementation manually, or derive it with a
| crate like thiserror: #[derive(Error)]
| enum MyError { #[error("Failed to read file")
| IoError(#[from] std::io::Error) // ... }
| fn foo() -> Result<(), MyError> { let data =
| std::fs::read("/some/file")?; // ... }
|
| You can also use helper methods on Result (like `map_err`)
| for inserting explicit conversions between error types:
| fn foo() -> Result<(), MyError> { let data =
| std::fs::read("/some/file").map_err(MyError::IoError)?;
| // ... }
| masklinn wrote:
| 1. That is a global static relationship rather than a local
| one dynamic one, which is the sense in which Go users use
| wrapping.
|
| 2. Idiomatic go type erases errors, so you're converting
| from `error` to `error`, hence type-directed conversions
| are not even remotely an option.
| alexchamberlain wrote:
| > That is a global static relationship rather than a
| local one dynamic one, which is the sense in which Go
| users use wrapping.
|
| In practice, the error type will be defined quite close
| to where the conversion is applied, so the static nature
| of it doesn't feel too big.
| NobodyNada wrote:
| `map_err` does not need to be type-directed; you can use
| an arbitrary function or closure. An enum variant can be
| used as a function mapping from the variant type to the
| error type, but we can do any arbitrary transformation:
| .map_err(|e| format!("Failed to read file: {e}")?;
|
| But the "idiomatic Go" way of doing things sounds a lot
| closer to anyhow in Rust, which provides convenience
| utilities for dealing with type-erased errors:
| use anyhow::{Result, Context}; fn foo() ->
| Result<()> { let data =
| std::fs::read("/some/file").context("Failed to read
| file")?; // ... }
| masklinn wrote:
| Yes, I know that, but the argument (which, again, I
| called dubious) is that in both cases it's much easier to
| do just e.g. fn foo() -> Result<()> {
| let data = std::fs::read("/some/file")?; // ...
| }
|
| whereas the current morass of Go's error handling means
| adding wrapping is not much more of a hassle.
|
| But of course even if you accept that assertion you can
| just design your version of `?` such that wrapping is
| easier / not wrapping is harder (as it's still something
| you want) e.g. make it `?"value"` and `?nil` instead of
| `?`, or something.
| tayo42 wrote:
| You need to implement from for every type of error then?
| That seems pretty tedious also.
| sa46 wrote:
| > The convenience of writing `?` means nobody will bother
| wrapping errors anymore.
|
| A thread from two days ago bemoans this point:
|
| https://news.ycombinator.com/item?id=44149809
| Pxtl wrote:
| Wait... x, err := strconv.Atoi("123") if
| err != nil { panic(err) } y, err :=
| strconv.Atoi("1234") fmt.Println("result:", x, y)
|
| > this also compiles and runs just fine but again you would
| have no idea something was wrong
|
| Okay, I don't use golang... but I thought ":=" was "single
| statement declare-and-assign".
|
| Is it not redeclaring "err" in your example on line 5, and
| therefore the new "err" variable (that would shadow the old err
| variable) should be considered unused and fail with 'declared
| and not used: err'
|
| Or does := just do vanilla assignment if the variable already
| exists?
| JamesSwift wrote:
| As I understand it, go has some special handling for this
| scenario because its so prevalent which special cases
| reassignment. The linked article touches on it
|
| > There are exceptions to this rule in areas with high "foot
| traffic": assignments come to mind. Ironically, the ability
| to redeclare a variable in short variable declarations (:=)
| was introduced to address a problem that arose because of
| error handling: without redeclarations, sequences of error
| checks require a differently named err variable for each
| check (or additional separate variable declarations)
| Pxtl wrote:
| ... I thought Go's whole deal was that you give up the
| expressiveness and power of overdesigned languages for
| simple, clean, "only one way to do it" semantics. That
| "special cases reassignment" where ':=' is sometimes a
| shadowing declaration and sometimes a reassignment sounds
| like the opposite of that.
| homebrewer wrote:
| The language is full of gotchas like that, you're
| expected to use the tooling to guardrail yourself,
| because having a proper type system or coherent syntax is
| "too complicated" (while learning dozens of patterns and
| weird tricks apparently isn't).
|
| go vet and this massive collection of linters bundled
| into a single binary are very popular: https://golangci-
| lint.run
|
| linters will warn you of accidental shadowing, among many
| other things.
| kbolino wrote:
| It's trickier than that, unfortunately. There has to be at
| least one new variable on the left side of := but any other
| variables that already exist _in the same scope_ will simply
| be assigned to. However, if you use := in a nested block,
| then the variable _is_ redeclared and shadows the outer-scope
| variable.
| Pxtl wrote:
| Thanks, I hate it.
| mseepgood wrote:
| - It has poor visibility, it hides control flow branches in a
| single statement / expression. That's one of the reasons Go got
| rid of the ternary operator in favor of an if statement where
| each branch has to be on its own line.
|
| - It isn't easily breakpointable.
|
| - It favors "bubbling up" as-is over enriching or handling.
| _jab wrote:
| 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 wrote:
| 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 }
| macintux wrote:
| 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?
| haiku2077 wrote:
| Just tried this and it appears to be valid in the compiler,
| formatter and golangci-lint
| _whiteCaps_ wrote:
| https://en.wikipedia.org/wiki/Yoda_conditions
|
| Works especially well in languages that can make
| assignments in if statements, e.g:
|
| if foo = 42 { }
| hnlmorg wrote:
| 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.
| vhcr wrote:
| Would be nice if code editors colored it differently so it's
| easier to see.
| prerok wrote:
| return nil
|
| would be clearer, I think. Seems like it's the same but would
| color differently in my editor.
| skybrian wrote:
| Good point. Perhaps it could also be solved in an editor with a
| collapsed notation like 'if err ... {'
| 9rx wrote:
| 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 wrote:
| 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.
| 9rx wrote:
| That reality may make the fundamental flaws of the if
| statement more noticeable, but at the end of the day the
| problem is still that the if statement itself is not great.
| If we're going to put in effort to improve upon it - and it
| is fair to say that we should - why only for a type named
| error?
| derefr wrote:
| 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.
| purpleidea wrote:
| 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.
| evmar wrote:
| Most discussions of language features immediately fall into the
| politician's syllogism:
|
| https://en.wikipedia.org/wiki/Politician%27s_syllogism
|
| I appreciate the Go language's general sense of conservatism
| towards change. Even if you're not a fan of it, I think it's
| admirable that there is a project staking out a unique spot in
| the churn-vs-stability design space. There are plenty of other
| projects that churn as fast as they can, which also has its pros
| and cons, and it's great to be able to see the relative outcomes.
|
| PS: it's kind of hilarious how the blog post is like "there are
| hundreds of proposals and miles of detailed analysis of these",
| vs the commenters here who are like "I thought about this for
| five minutes and I now have an idea that solve everything, let me
| tell you about it".
| mseepgood wrote:
| Sometimes doing nothing is the right thing to do. (Quote from
| Until Dawn)
|
| Go chose not to change the error handling - Nature remained in
| balance.
| ummonk wrote:
| I'd happily come up with criticisms of any specific proposal
| and bikeshed it, but any one of these proposals would be
| preferable to the status quo.
|
| I'd understand if they decided they needed more time to
| continue iterating on and analyzing proposals to find the right
| solution, but simply declaring that they'll just suspend the
| whole effort because they can't come to a consensus is rather
| infuriating.
| sa46 wrote:
| "Simply declaring" is inaccurate description of the Go team's
| decision. The team built several proposals, reviewed dozens
| more, and refined the process by gathering user feedback in
| multiple channels.
|
| The Go team thoroughly explored the design space for seven
| years and did not find community consensus.
| ummonk wrote:
| There are two possibilities.
|
| 1) There isn't consensus that improved syntax for error
| handling is needed in the first place. If that is the case,
| they should just say so, instead of obfuscating by focusing
| on the number of proposals and the length of the process.
|
| 2) There is consensus about a need for improved error
| handling syntax, but after seven years of proposals they
| haven't been able to find community consensus about the
| best way to add said syntax. That would mean that improved
| syntax for error handling is necessary, but the Go team is
| understandably hesitant to push forward and lock in a
| potentially inferior solution. If that is the case, then
| would be reason to continue working on improved syntax for
| error handling, so as to find the best solution even if it
| takes a while.
| ddoice wrote:
| Oh No! Is a much better name for error handling.
| ajkjk wrote:
| Okay here's my idea, not found on the list in the article, what
| do you think:
|
| You add a visualization sugar via an IDE plugin that renders
| if/else statements (either all of them or just error cases) as
| two separate columns of code --- something like
| x = foo(); if (x != nil) | else <happy
| case> | <error case>
|
| And then successive error cases can split further, making more
| columns, which it is up to the IDE to render in a useful way.
| Underneath the representation-sugar it's still just a bunch of
| annoyingly nested {} blocks, but now it's not annoying to look
| at. And since the sugar is supported by the language developers,
| everyone is using the same version and can therefore rely on
| other developers seeing and maintaining the readability of the
| code in the sugared syntax.
|
| If the error case inside a block returns then its column just
| ends, but if it re-converges to the main case then you visualize
| that in the IDE as well. You can also maybe visualize some
| alternative control flows: for instance, a function that starts
| in a happy-path column but at all of its errors jumps over into
| an error column that continues execution (which in code would
| look like a bunch of `if (x=nil) { goto err; }` cases.
|
| Reason for doing it this way: logical flow within a single
| function forms a DAG, and trying to represent it linearly is
| fundamentally doomed. I'm betting that it will eventually be the
| case that we stop trying to represent it linearly, and we may as
| well start talking about how to do it now. Sugar is the obvious
| approach because it minimizes rethinking the underlying language
| and allows for you to experiment with different approaches.
| Someone wrote:
| I would simplify that to x = foo() ||| <error
| case> <happy case>
|
| (With the specific symbol used in lieu of _|||_ to be decided)
|
| That is shorter and keeps the happy path unindented, even if it
| has additional such constructs, for example x =
| foo() ||| return Error(err, "foo failed") y = bar() |||
| return Error(err, "bar failed")
| layer8 wrote:
| I think that IDE functionality is fine for writing code, but
| shouldn't be imposed for the UX of reading code, because code
| reading also happens a lot outside of IDEs, because it
| constrains the choice of editors, and because it creates
| fundamentally different "modes" of source code presentation.
| The visualizations will start to appear in comment threads like
| this one, and in other publications on the web, but copying
| them and pasting them into an editor will not work (will be
| invalid syntax). It creates unnecessary complications across
| the whole ecosystem. Language syntax should stand on its own,
| and shouldn't need crutches like that to make it ergonomic to
| read.
| ajkjk wrote:
| I have the opposite opinion, I guess! We've been trying to
| solve everything with language syntax for a long time and
| it's a bit of a dead end, as the OP shows. Better to start
| trying new things.
|
| Anyway you can always copy paste it in the normal linear
| format.
| zahlman wrote:
| Watching the process of thinking about this from the outside,
| somehow reminds me of my experience on the inside of the Python
| community trying to figure out packaging.
| te_chris wrote:
| The way elixir conventionally uses tuples and pattern matching is
| really good.
| threemux wrote:
| 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.
| anentropic wrote:
| 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?
| ok_dad wrote:
| 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.
| tialaramex wrote:
| I don't see how Try (the ? operator) is hidden control
| flow. It's terse, but it's not hidden.
| ok_dad wrote:
| I personally agree, but I'm not the go team. The hidden
| control flow was specifically called out but about the
| try keyword. I like the ? and similar ways of checking
| nulls, but personally I don't mind the verbosity in go,
| even if there are footguns.
| jchw wrote:
| IMO: because it behaves like structured control flow
| (i.e. there is a branch) but it doesn't look like
| structured control flow (i.e. it doesn't look like there
| is a branch; no curly braces). I don't think there's a
| single other case in the Go programming language: it
| doesn't even have the conditional ternary operator, for
| example.
| LoganDark wrote:
| `return` doesn't have braces either.
| jchw wrote:
| Closest thing to a real interblock branch without braces,
| IMO, is `break` and `continue`, but those are both at
| least lone statements, not expressions. It "looks like"
| control flow. Personally, I don't count `return`, I view
| it as it's own thing from a logical standpoint. Obviously
| if we were talking about literal CPU doing a jump, well
| then a lot of things would count, but that's not what I
| mean in the frame of structured control flow and more in
| the realm of implementation details.
| 9rx wrote:
| Even the laziest programmer is going to want to wrap the
| error with another error, and usually you will want to do
| more than that.
|
| You can put that in-band, with something like:
| v := funcWithError()? err := { return
| fmt.Errorf("lazy programmer wrapping: %w", err) }
|
| But in that case what have you really gained?
|
| Or you can do some kind of magic to allow it to happen
| out of band: // found somewhere else in
| the codebase func wrapErr(err error) error {
| if err == nil { return nil }
| return fmt.Errorf("lazy programmer wrapping: %w", err)
| } v := funcWithError()?(wrapErr)
|
| But that's where you start to see things hidden.
| kiitos wrote:
| Currently if you want to return from a function/method
| you need to type `return` in the source code. And return
| is a single expr, it can't be chained or embedded, and in
| almost all cases it exists on its own line in the file.
| This is an important invariant for Go, even if you don't
| understand or buy its motivation. `?` would fundamentally
| change that core property of control flow. In short,
| chaining is not considered to be a virtue, and is not
| something that's desired.
| skywhopper wrote:
| That only covers one tiny case among several possible
| error flows. Why add special syntax for that?
| hackingonempty wrote:
| 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.
| skywhopper wrote:
| 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 wrote:
| 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 wrote:
| 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.
| 9rx wrote:
| _> 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.
| karmakaze wrote:
| At least Java supports covariance and contravariance where
| Go only supports invariant generics.
| PaulHoule wrote:
| Java has evolved to contain much of "ML the good parts"
| such as that languages like Kotlin or Scala that offer a
| chance to be just a bit better in the JVM look less
| necessary
| platinumrad wrote:
| 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.
| evntdrvn wrote:
| hahaha :D
| nine_k wrote:
| IDK, Python was fine grabbing list comprehensions from
| Haskell, yield and coroutines from, say, Modula-2, the
| walrus operator from, say, C, large swaths from Smalltalk,
| etc. It does not matter if the languages are related; what
| matters is whether you can make a feature / approach fit
| the rest of the language.
| munificent wrote:
| 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.
| yusina wrote:
| 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 wrote:
| 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?
| yusina wrote:
| > I don't get why people keep thinking it was forgotten
|
| Well, I replied to a post that gave a link to a document
| that supposedly exhaustively (?) listed all alternatives
| that were considered. Monads are not on that list. From
| that, it's easy to come to the conclusion that it was not
| considered, aka forgotten.
|
| If it was not forgotten, then why is it not on the list?
|
| > Is this really a good idea for a language that can't
| express monads naturally?
|
| That's a separate question from asking why people think
| that it wasn't considered. An interesting one though. To an
| experienced Haskell programmer, it would be worth asking
| why not take the leap and make it easy to express monads
| naturally. Solving the error handling case elegantly would
| just be one side effect that you get out of it. There are
| many other benefits, but I don't want to make this into a
| Haskell tutorial.
| jchw wrote:
| It's not an exhaustive list of every possible way to
| handle errors, but it is _definitely_ , IMO, roughly an
| exhaustive list of possible ways Go could reasonably add
| new error handling tools in the frame of what they
| already have. The reason why monads and do notation don't
| show up is because if you try to write such a proposal it
| _very_ quickly becomes apparent that you couldn 't really
| add it to the Go programming language without other,
| _much_ bigger language change proposals (seriously, try
| it if you don 't believe me.) And for what it's worth,
| I'm _not_ saying they shouldn 't, it's just that you're
| taking away the wrong thing; I am absolutely 100% certain
| this _has_ come up (in fact I think it came up relatively
| early in one of the GitHub issues), but it hasn 't
| survived into a proposal for a good reason. If you want
| this, I believe you can't start with error handling
| first; sum types would probably be a better place to
| start.
|
| > That's a separate question from asking why people think
| that it wasn't considered. An interesting one though. To
| an experienced Haskell programmer, it would be worth
| asking why not take the leap and make it easy to express
| monads naturally. Solving the error handling case
| elegantly would just be one side effect that you get out
| of it. There are many other benefits, but I don't want to
| make this into a Haskell tutorial.
|
| Hmm, but you could say that for any idea that sounds
| good. Why not add a borrow checker into Go while we're at
| it, and GADTs, and...
|
| Being blunt, this is just incorrect framing. Concepts
| like monads and do notation are not inherently "good" or
| "bad", and neither is a language feature like a borrow
| checker (which also does _not_ mean you won 't miss it
| when it's not there in languages like Go, either). Out of
| context, you can't judge whether it's a good idea or not.
| In context, we're talking about the Go programming
| language, which is _not_ a blank slate for programming
| language design, it 's a pre-existing language with
| _extremely_ different values from Haskell. It has a pre-
| existing ecosystem built on this. Go prioritizes
| simplicity of the language and pragmatism over
| expressiveness and rich features nearly every time. This
| is not everyone 's favorite tradeoff, but also,
| programming language design is not a popularity contest,
| nor is it an endeavor of mathematical elegance. Designers
| have goals, often of practical interest, that require
| trade-offs that by definition not everyone will like. You
| can't just pretend this constraint doesn't exist or isn't
| important. (And yes we know, Rob Pike said once in _2012_
| that Go was for idiots that can 't understand a brilliant
| language. If anyone is coming here to make sure to reply
| that under each comment as usual on HN, consider it pre-
| empted.)
|
| So to answer the question, would it be worth the leap to
| make it easy to express monads naturally in Go?
| Obviously, this is a matter of opinion and not fact, but
| I think this is well beyond the threshold where there is
| room for ambiguity: _No._ It just does not mesh with it
| at all, does not match nearly any other decision made
| anywhere else with regards to syntax and language
| features, and just generally would feel utterly out of
| place.
|
| A less general version of this question might be, "OK:
| how about just sum types and go from there?"--you could
| _probably_ add sum types and express stuff like Maybe
| /Either/etc. and add language constructs on top of this,
| but even that would be a pretty extreme departure and
| basically constitute a totally new, distinct programming
| language. Personally, I think there's only one way to
| look at this: either Go should've had this and the
| language is basically doomed to always have this flaw,
| _or_ there is room in the space of programming languages
| for a language that doesn 't do this without being
| strictly worse than languages that do (and I'm thinking
| here in terms of not just elegance or expressiveness but
| of the second, third, forth, fifth... order effects of
| such a language design choice, which become increasingly
| counter-intuitive as you follow the chain.)
|
| And after all, doesn't this _have_ to be the case? If
| Haskell is the correct language design, then we already
| have it and would be better off writing Haskell code and
| working on the GHC. This is not a sarcastic remark: I don
| 't rule out such dramatic possibilities that some
| programming languages might just wind up being "right"
| and win out in the long term. That said, if the winner is
| going to be Haskell or a derivative of it, I can only
| imagine it will be a while before that future comes to
| fruition. A long while...
| joaohaas wrote:
| 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.
| philosophty wrote:
| 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.
| throwawaymaths wrote:
| 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.
| philosophty wrote:
| "To be fair there are lots of pilots who have flown
| multiple aircraft at an expert level that complain about
| the Airbus A380 - in the same ways - as well! They might
| not be expert airplane designers, but they have a breadth
| of experience, and even some of them have created their own
| model airplanes too."
|
| Yes, non-experts can have valid criticisms but more often
| than not they're too ignorant to even understand what
| trade-offs are involved.
| throwawaymaths wrote:
| see there you go again assuming. im talking about people
| who have written programming languages that are used in
| prod with millions of users, not people with toy
| languages.
|
| is the entire go community this toxically ignorant?
| philosophty wrote:
| You said nothing that indicates you were referring to
| other expert language designers.
|
| This entire thread is full if amateurs making ignorant
| comments. So what expert criticisms are you referring to?
|
| You accused me of "gaslighting" and being "toxically
| ignorant" while I have been entirely civil.
| unclad5968 wrote:
| I don't use Go, but I actually like Go's error handling and I
| think multiple return values is a better solution than any other
| language I've used. So much so, I've basically adopted it in my
| c++ code using std::pair. Errors are a value, and the abstraction
| over that is unnecessary in my opinion. Rust's result type is
| just syntactic sugar around the multiple return value approach. I
| don't care for the syntactic sugar, and doing many things in few
| lines of code isn't valuable to me, but I suspect this is why
| people love rust's error handling.
| hmry wrote:
| > Rust's result type is just syntactic sugar around the
| multiple return value approach
|
| That's really not true. Multiple return values means you always
| need to return some return value and some error value, even if
| they are dummy values (like nil). While a result type / sum
| type genuinely only contains one branch, not the other.
|
| If you had a language that didn't have nil, it would genuinely
| be impossible to emulate sum type like behavior on top of
| multiple return values. It serves as an escape hatch, to create
| a value of some type when you don't actually have a meaningful
| value to give.
|
| std::variant / std::expected / std::optional aren't syntactic
| sugar for std::pair either.
| abtinf wrote:
| IMHO, the actual problem with go error handling isn't the error
| handling at all -- it's that multiple return values aren't a
| first class construct. With proper tuple handling and Go's
| approach to generics, a lot of these issues would just disappear.
| JamesSwift wrote:
| This also grinds my gears when converting multiple-return
| functions to returns-a-channel functions. Generics help with
| that now.
| kubb wrote:
| TLDR: we didn't fix it for such a long time that now it's too
| late to fix it. We won't be fixing it, thanks!
|
| Edit: looking at the results of their H1 2024 survey, they're
| making a hard turn into AI, and most likely will be developing AI
| libraries to close the gap with Python.
|
| Don't expect language features.
| codehakase wrote:
| Can't believe we're going to get GTA 6 before an agreed upon
| (cleaner) error handling pattern in Go.
| HippoBaro wrote:
| Just to add my two cents--I've been writing Go professionally for
| about 10 years, and neither I nor any of my colleagues have had
| real issues with how Go handles errors.
|
| Newcomers often push back on this aspect of the language (among
| other things), but in my experience, that usually fades as they
| get more familiar with Go's philosophy and design choices.
|
| As for the Go team's decision process, I think it's a good thing
| that the lack of consensus over a long period and many attempts
| can prompt them to formally define a position.
| dangoodmanUT wrote:
| This, it's always the new people complaining about error
| handling.
|
| I have many things to complain about for other languages that
| I'm sure are top-tier complaints too
| tines wrote:
| I appreciate the argument that things can often be difficult
| for noobs but actually fine or better than alternatives once
| you get used to them.
|
| But on the other hand, people who are "used to the way things
| are" are often the worst people to evaluate whether changes
| are beneficial. It seems like the new people are the ones
| that should be listened to most carefully.
|
| I'm not saying the Go team was wrong in this decision, just
| that your heuristic isn't necessarily a good one.
| jchw wrote:
| This logic mostly only makes sense if your goal is
| primarily to grow the audience and widen the appeal,
| though. I think at this stage in the Go programming
| language's lifespan, that is no longer the goal. If
| anything, Go has probably started to saturate its own sweet
| spot in some ways and a lot of complaints reveal a
| difference in values more than they do opportunity for
| improvement.
|
| To me, it makes sense for the Go team to focus on improving
| Go for the vast majority of its users over the opinions of
| people who don't like it that much in the first place.
| There's millions of lines of code written in Go and those
| are going to have to be maintained for many years. Of
| utmost priority in my mind is making Go code more correct
| (i.e. By adding tools that can make code more correct-by-
| construction or eliminate classes of errors. I didn't _say_
| concurrency safety, but... some form of correctness
| checking for code involving mutexes would be really nice,
| something like gVisor checklocks but better.)
|
| And on that note, if I could pick something to prioritize
| to add to Go, it would probably be sum types with pattern
| matching. I don't think it is extremely likely that we will
| see those, since it's a massive language change that isn't
| exactly easy to reconcile with what's already here. (e.g. a
| `Result` type would naturally emerge from the existence of
| sum types. Maybe that's an improvement, but _boy_ that is a
| non-trivial change.)
| jiehong wrote:
| It's fun, because when a newcomer joins a team, people tend
| to remind them that their bison is fresh and they might be
| seeing pain we got accustomed to. That's usually said in a
| positive manner.
| tialaramex wrote:
| I'm intrigued as to whether "bison" here is a metaphor (for
| what?) or a cupertino (an error introduced by auto-correct
| or predictive text)
| abtinf wrote:
| I have a similar level of experience with Go, and I would go so
| far as to say it is in fact one of the best features of the
| language.
|
| I wouldn't be surprised that when the pro-exception-handling
| crowd eventually wins, it will lead to hard forks and severe
| fragmentation of the entire ecosystem.
| jchw wrote:
| To be honest, I really don't believe that will happen in the
| future. All of the proposals pretty much just add syntactical
| sugar, and even those have failed to gain consensus.
| ummonk wrote:
| Yeah once you've been using it long enough for the Stockholm
| syndrome to set in, you come to terms with the hostile parts of
| the language.
| janderland wrote:
| I suspect a lot of us don't have strong feelings either way
| and don't find the verbosity "hostile". No need for Stockholm
| syndrome if you don't feel like a prisoner.
|
| Of course you may have been joking, in which case "haha". xD
| zarzavat wrote:
| That's just survivorship bias isn't it? The newcomers who find
| Go's design and/or leadership obnoxious get a job that doesn't
| involve doing something that they dislike.
| arp242 wrote:
| That's okay. Not everyone needs to like Go. Pleasing every
| programmer on the planet is an unreasonable thing to ask for.
| It's also impossible because some preferences conflict.
| zarzavat wrote:
| After over a decade of people bringing up the issue in
| almost every single thread about Go, it's time to give the
| language what it deserves: no more constructive feedback,
| snarky dismissals only.
| danenania wrote:
| I have no problem with Go's error handling. It's not elegant, but
| it works, and that's very much in keeping with the pragmatic
| spirit of Go.
|
| I'm actually kind of surprised that it's the top complaint among
| Go devs. I always thought it was more something that people who
| don't use Go much complain about.
|
| My personal pet issue is lack of strict null checks--and I'm
| similarly surprised this doesn't get more discussion. It's a way
| bigger problem in practice than error handling. It makes programs
| crash in production all the time, whereas error handling is
| basically just a question of syntax sugar. _Please_ just give me
| a way to mark a field in a struct required so the compiler can
| eliminate nil dereference panics as a class of error. It's opt-in
| like generics, so I don't see why it would be controversial to
| anyone?
| CactusRocket wrote:
| I have 2 problems.
|
| It's too easy to accidentally write `if err == nil` instead of
| `if err != nil`. I have even seen LLMs erroneously generate the
| first instead of the latter. And since it's such a tiny
| difference and the code is riddled with `if err != nil`, it's
| hard to catch at review time.
|
| Second, you're not forced by the language to do anything with
| the error at all. There are cases where `err` is used in a
| function that not handling the `err` return value from a
| specific function silently compiles. E.g. x,
| err := strconv.Atoi(s1) if err != nil {
| panic(err) } y, err := strconv.Atoi(s2)
| fmt.Println(x, y)
|
| I think accidentally allowing such bugs, and making them hard
| to spot, is a serious design flaw in the language.
| danenania wrote:
| I guess those are fair criticisms in the abstract, but
| personally I can't recall a single time either has caused a
| bug for me in practice. I also can't ever recall seeing an
| LLM or autocomplete mess it up (just my personal experience--
| I'm sure it can happen).
| masklinn wrote:
| > It's opt-in like generics, so I don't see why it would be
| controversial to anyone?
|
| It "breaks" the language in fundamental ways -- much more
| fundamental than syntactic sugar for error handling -- by
| making zero values and thus zero initialisation invalid.
|
| You even get this as a fun interaction with generics:
| func Zero[T any]() T { var v T return v
| }
| danenania wrote:
| I don't see how it breaks anything if it's opt-in. By default
| you get the current behavior with zero value initialization
| if that's what you want (and in many cases it is). But if
| you'd rather force an explicit value to be supplied, what's
| the harm?
| masklinn wrote:
| > I don't see how it breaks anything if it's opt-in?
|
| Zero values are a fundamental, non-optional, "feature" of
| Go.
|
| > But if you'd rather force an explicit value to be
| supplied, what's the harm?
|
| What happens if you use the function above with your type?
|
| Or reflect.Zero?
| danenania wrote:
| If it would only complain on struct literals that are
| missing the value (and force a nil check before access if
| the zero value is nil to prevent panics), that would be
| enough for me. In that case, your Zero function and
| reflect.Zero can keep working as-is.
| masklinn wrote:
| Then I fail to see the point, that is trivial to lint,
| just enable the exhauststruct checker.
| danenania wrote:
| I wasn't aware of it--will check it out, thanks.
| ziml77 wrote:
| They admit it's contrived, but this isn't very convincing
| func printSum(a, b string) error { x, err :=
| strconv.Atoi(a) if err != nil {
| return fmt.Errorf("invalid integer: %q", a) }
| y, err := strconv.Atoi(b) if err != nil {
| return fmt.Errorf("invalid integer: %q", b) }
| fmt.Println("result:", x + y) return nil }
|
| It's not adding anything that the Atoi function couldn't have
| reported. That's a perfect case for blindly passing an error up
| the stack.
| nasretdinov wrote:
| It does, it says which one of the two integers was incorrect
| Zambyte wrote:
| Why can't Atoi report that?
| nasretdinov wrote:
| I think Atoi actually does say that, but it's just a toy
| example. Most often functions outside the standard library
| don't contain the arguments in their error values
| tsimionescu wrote:
| > 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 wrote:
| 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 wrote:
| > 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...
| veggieroll wrote:
| Error handling is one of my favorite parts of Go. The haters can
| rip `if err != nil { return fmt.Errorf("error doing thing: %w",
| err) }` from my cold dead hands.
| ummonk wrote:
| You don't use gofmt?
| tom_ wrote:
| The whole point of gofmt is that you type in whatever you
| like and gofmt sorts it out for you. So if you're typing your
| code in a HN comment box, you'd surely just enter it roughly
| like that - though i recommend not even including the
| syntactically irrelevant spaces shown here. Your space bar
| does have a MTBF you know, and it's measured in
| activations... let gofmt take the strain. It can waste your
| screen space for you!
|
| gofmt is the good bit about working in Go. Pretty much
| everybody uses it, and so you can use it too. Some other
| languages have similar tools, but they're not as pervasive,
| so it's far too easy to end up in a situation where you can't
| use the tool because it would just make too much of a mess of
| the inconsistently manually-formatted stuff that's already
| there.
| thayne wrote:
| Error handling is the thing I hate the most about go. And none
| of the serious proposals I've seen would remove your ability to
| continue using `if err != nil`
| veggieroll wrote:
| I have yet to see a proposal that retains what I love about
| the status quo: conscientious error handling.
|
| The language's status quo forces everyone to think about
| errors more deeply than in other languages and acknowledges
| that the error case is as critical and worthy of the
| programmer's attention.
| sedatk wrote:
| > forces everyone to think about errors more deeply than in
| other languages
|
| Not really. Rust also forces you think deeply about errors
| but don't bother you with verbose syntax. I think Swift was
| also similar.
| hackingonempty wrote:
| Generators and Goroutines have keywords/syntax in Golang but now
| they don't want to pile on more to handle errors. They could have
| had one single bit of syntactic sugar, "do notation", to handle
| all three and more if they had considered it from the beginning
| but it seems too late if the language designers are even aware of
| it. TFA says "If you're wondering if your particular error
| handling idea was previously considered, read this document!" but
| that document references languages with ad-hoc solutions (C++,
| Rust, Swift) and does not reference languages like Haskell,
| Scala, or OCaml which have the same generic solution known as do-
| notation, for-comprehensions, and monadic-let respectively.
|
| For example instead of func printSum(a, b string)
| error { x, err := strconv.Atoi(a) if err !=
| nil { return err } y, err :=
| strconv.Atoi(b) if err != nil { return
| err } fmt.Println("result:", x + y)
| return nil }
|
| they could have something like this: func
| printSum(a, b string) result[error, unit] { return for
| { x <- strconv.Atoi(a) y <-
| strconv.Atoi(b) } yield fmt.Println("result:", x + y)
| }
|
| which desugars to: func printSum(a, b string)
| result[error, unit] { return
| strconv.Atoi(a).flatMap(func(x string) result[error, unit] {
| return strconv.Atoi(b).map(func(y string) unit {
| return fmt.Println("result:", x + y) } }
| }
|
| and unlike ad-hoc solutions this one bit of syntax sugar, where
| for comprehensions become invocations of map, flatMap, and filter
| would handle errors, goroutines, channels, generators, lists,
| loops, and more, because monads are pervasive:
| https://philipnilsson.github.io/Badness10k/escaping-hell-wit...
| VirusNewbie wrote:
| I absolutely agree with this, and would be better than all of
| the other proposals.
|
| Did anyone propose this in one of the many error handling
| proposals?
| bccdee wrote:
| Here's a non-syntactic suggestion. Could we just patch gofmt to
| permit this: if err != nil { return nil, err; }
|
| as a well-formatted line of go? Make it a special case somehow.
|
| My only big problem with if err != nil is that it eats up 3 lines
| minimum. If we could squish it down to 1, I'd be much more
| content.
| verdverm wrote:
| What if I wrap the error? Should that be squashed to one line?
| What is the heuristic?
| bccdee wrote:
| Sure, why not? Let the programmer decide if they want one
| line or three lines; the tool can permit both. Gofmt is line-
| length-agnostic--breaking up long lines is already considered
| to be the programmer's responsibility.
| mseepgood wrote:
| How would you set a breakpoint on the error return case?
| bccdee wrote:
| You wouldn't. Rust's ? operator doesn't permit that either.
| If you need to put a breakpoint there, put a line break
| there.
| mseepgood wrote:
| One reason I consider Rust's approach worse than Go's.
| GoatInGrey wrote:
| The rub there is that you'll have varying styles in which error
| handling statements appear in your code. With simplistic
| instances appearing one way and less simplistic ones appearing
| similarly or differently depending on where each lands on the
| complexity spectrum. The idiomatic approach is for all
| instances to be written in the same way regardless of technical
| nuances.
|
| All of that aside, I've come to learn that passing errors up
| the call stack without any wrapping or handling is a code
| smell. It is less than useless for me to attempt setting the
| value of cell A1 in an Excel sheet to "Foo" and then receive an
| out-of-range error because the developer made no attempt to
| even inform me of the context around the error. Let alone
| handling the error and attempting to correct state before
| giving up.
|
| In my Excel example, the cause of the error was a data
| validation problem a couple columns over (F or so). The error
| was legitimately useless in troubleshooting.
| nilirl wrote:
| Error handling is some of the least fun parts of writing code. In
| all languages.
|
| But in any case, why so much fear of being wrong?
|
| > we have fine-grained control over the language version via
| go.mod files and file-specific directives
|
| And that may be the real truth of it: Error handling in Go just
| isn't ... that much of a problem to force action?
| scoodah wrote:
| > And that may be the real truth of it: Error handling in Go
| just isn't ... that much of a problem to force action?
|
| I you are right that this is the truth of it. Error handling
| just isn't that big a problem. 13% reported it on the survey
| cited. That doesn't seem that significant. And honestly, after
| writing Go, I barely notice error handling as I'm
| writing/reading code anyway. If anything I appreciate it a bit
| more than exceptions.
|
| Always something that can be complained about. But it doesn't
| mean every complaint is a huge deal.
| arccy wrote:
| considering the recent post [1] on system correctness in amazon
| quoting "In 2014, Yuan et al. found that 92% of catastrophic
| failures in tested distributed systems were triggered by
| incorrect handling of nonfatal errors."
|
| perhaps it's a good thing that error handling is so explicit, and
| treated as a regular code path.
|
| [1]: https://news.ycombinator.com/item?id=44135638
| honkycat wrote:
| awesome I love noisy boilerplate in my code, it isn't annoying at
| all
| jamamp wrote:
| 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 wrote:
| 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 wrote:
| 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 wrote:
| > 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 wrote:
| 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.
| etra0 wrote:
| Yet the problem still has happened on big projects:
|
| https://news.ycombinator.com/item?id=36398874
| pphysch wrote:
| > 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.
| ignoramous wrote:
| At this rate, I suspect Go2 is an ideas lab for what's _never_
| shipping.
| melodyogonna wrote:
| Go's error handling fade away after a month of consistent use, I
| would not risk backwards compatibility over this. In fact, I like
| the explicit error handling.
| thayne wrote:
| The proposals are not backwards incompatible. And just because
| you get used to something doesn't mean it is good.
|
| And FWIW, my hatred of go error handling has not diminished
| with increased usage.
| sedatk wrote:
| > I like the explicit error handling
|
| You mean "verbose error handling". All other proposals are also
| explicit, just not as verbose.
| TZubiri wrote:
| Love it, focus on building with what we have.
|
| Resist enshittification, the greatest advantage in foundational
| software is sometimes saying no to new features.
| d3ckard wrote:
| 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.
| throwawa14223 wrote:
| 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 wrote:
| 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.
| bravesoul2 wrote:
| You can at least in Go do this:
|
| r, err := f()
|
| r := f()
|
| _, err := f()
| 9rx wrote:
| 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.
| throwawaymaths wrote:
| have a single return value and if you really need MRV,
| return as a tuple type, which you could destructure.
|
| (this is what zig does)
| 9rx wrote:
| But then why accept multiple input arguments? Why not
| limit to a single argument, accepting a tuple where
| multiple arguments are necessary, for input too?
|
| Where multiple input arguments are present, not having
| multiple output arguments is just strange.
| throwawaymaths wrote:
| Exactly. Why not make multiple argument function call
| syntatic sugar over a ~single argument tuple call?
|
| https://ziglang.org/documentation/0.14.1/#call
| knutzui wrote:
| 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)) }
| prerok wrote:
| Well, if fetchBar requires fetchFoo to complete
| successfully, you still somehow have to handle it.
|
| That said, there are libraries out there that implement
| Result as generic type and it's fine working with them, as
| well.
|
| I don't see what the hubbub is all about.
| renewiltord wrote:
| I am very entertained by this. The Golang community bikeshedded
| their way into the status quo. Hahaha, I have to say that's a
| pretty good move by the steering org. Punishes bikeshedding.
| pphysch wrote:
| It could be seen as a big waste of time, but it sets a good
| precedent.
| stackedinserter wrote:
| IDE's could help and just hide standard `if err != nil return
| fmt.Errorf("sh: %w", err)` blocks for us, or show them in some
| distinct way, like question mark after the statement.
| Mond_ wrote:
| 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?
| GiorgioG wrote:
| 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.
| neild wrote:
| > 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 wrote:
| 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.
| VirusNewbie wrote:
| Agreed, 100%.
|
| We're both Googlers here and this is so disappointing to be let
| down again by the Go team.
| eximius wrote:
| Ah, shame. `foo := someFallibleMethod()?` would have been nice
| for when you don't want to handle/wrap the error.
|
| I'm not super strongly against the constant error checking - I
| actually think it's good for code health to accept it as inherent
| complexity - but I do think some minor ergonomics would have been
| nice.
| pikzel wrote:
| They still haven't solved shadowing. a, err :=
| foo() b, err := bar() if err != nil { // oops, forgot
| to handle foo()'s err }
|
| This is the illusion of safe error handling.
| AnimalMuppet wrote:
| I would be _astonished_ if there isn 't an automated tool to
| check for that at the push of a button. I would be mildly
| surprised if there isn't a compiler flag to check for it.
| arp242 wrote:
| Not a compiler check, but staticcheck is widely used:
| % staticcheck test.go test.go:7:2: this value of err
| is never used (SA4006)
| dmitshur wrote:
| Yeah, as one data point,
| https://staticcheck.dev/docs/checks/#SA4006 has existed since
| 2017.
| _benton wrote:
| It's fairly obvious when writing Go that `err` is being
| shadowed and needs to be checked after each expression. You
| should be wrapping them anyways!
| agumonkey wrote:
| makes you miss the `or die` idiom :)
| anttiharju wrote:
| I was actually bit scared, so read through the whole post and am
| happy with the conclusion.
|
| Go is very readable in my experience. I'd like to keep it that
| way.
| cherryteastain wrote:
| All they have to do is to expand generics to support generic
| methods so we can have monadic operations like in C++'s
| std::expected or Rust's Result, like func (r
| Result[T, E]) AndThen[OtherT any](func(T) Result[OtherT, E])
| Result[OtherT, E] { ... }
|
| which would enable error handling like sum := 0
| parseAndAdd := func(s string) (func(string)Result[int, error]) {
| /* returns func which parses and adds to sum */ } return
| parseAndAdd(a)().AndThen(parseAndAdd(b))
|
| There's a reason why every other language is converging to that
| sort of functional setup, as it opens up possibilities such as
| try-transform generics for ranges.
| tslocum wrote:
| Seems strange that Rust's "?" gets a mention syntax-wise, but
| nothing is said about sum types coming to Go. Go's verbose error
| handling and lack of sum types are my only gripes with the
| language. It would be nice to see both addressed using Rust's
| Result type as a model.
| reader_1000 wrote:
| > 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.
|
| Languages with stack traces gives this to you for free, in Go,
| you need to implement it every time. OK, you may be disciplined
| developer where you always augment the error with the details but
| not all the team members have the same discipline.
|
| Also the best thing about stack traces is that it gives you the
| path to the error. If the error is happened in a method that is
| called from multiple places, with stack traces, you immediately
| know the call path.
|
| I worked as a sysadmin/SRE style for many years and I had to
| solve many problems, so I have plenty of experience in
| troubleshooting and problem solving. When I worked with stack
| traces, solving easy problems was taking only 1-2 minutes because
| the problems were obvious, but with Go, even easy problems takes
| more time because some people just don't augment the errors and
| use same error messages which makes it a detective work to solve
| it.
| Pxtl wrote:
| I've never used Go but having used other languages with
| exception-based error handling, I get why Go decided to go in a
| different direction... but reading this over...
|
| Okay, so surely some syntactic sugar could make it more pleasant
| than the if (err != nil) { return nil,
| err }
|
| repeated boilerplate. Like, if that return is a tagged union you
| could do some kind of pattern matching?
|
| ... oh, Go doesn't have sum-types. Or pattern matching.
|
| Could you at least do some kind of generic error handler so I can
| call y := Handle(MyFuncThatCanReturnError(x))
|
| ?
|
| ... Okay, GoLang does not have tuples. Multiple returns must be
| handled _separately_.
|
| Okay could I write some kind of higher-order function that
| handles it in a generic way? Like y :=
| InvokeWithErrorHandler(MyFuncThatCanReturnError, x)
|
| ?
|
| No? That's not an option either?
|
| ... why do you all do this to yourselves?
| tail_exchange wrote:
| This doesn't actually makes the process simpler.
|
| Error handling in Go is not just writing "if err != nil {
| return nil, err }" for every line. You are supposed to enrich
| the error to add more context to it. For example:
| result, err := addTwoNumbers(a, b) if err != nil {
| return fmt.Errorf("addTwoNumbers(%d, %d) = %v", a, b, err)
| }
|
| This way you can enrich the error message and say what was
| passed to the function. If you try to abstract this logic with
| a "Handle" function, you'll just create a mess. You'll save
| yourself the time of writing an IF statement, but you'll need a
| bunch of arguments that will just make it harder to use.
|
| Not to mention, those helper functions don't account for cases
| where you don't just want to bubble up an error. What if you
| want to do more things, like log, emit metrics, clean up
| resources, and so on? How do you deal with that with the
| "Handle()" function?
| Bratmon wrote:
| Watching go people complaining about how other languages
| encourage bubbling errors up is always hilarious to me because
| there is literally nothing you can do with errors in go except
| bubble them up, log them, or swallow them.
|
| Even the article considers "handling" an error to be synonymous
| with "Adding more text and bubbling it up"!
| gwd wrote:
| I mean, yeah, most of the time what you do is add more text and
| bubble up. But:
|
| 1. The very fact that adding more text isn't really any more
| verbose than not encourages you to add more text, making errors
| more informative.
|
| 2. A non-negligible amount of times you do something else:
| carry on, or do something specific based on what kind of error
| it was. For instance, ignore an error if it's in a certain
| class; or create the file if it didn't exist; and so on.
|
| Forcing the error handling doesn't seem to me that different
| than forcing you to explicitly cast between (say) int and
| int64. Part of me is annoyed with that too, but then I have
| PTSD flashbacks from 20 years of C programming and appreciate
| it.
| tail_exchange wrote:
| > there is literally nothing you can do with errors in go
| except bubble them up, log them, or swallow them
|
| You can also add additional context to the error before
| bubbling it up. But yes, that part of the point. Instead of
| bubbling them up, the programmer should instead reflect on
| whether it is better than just log and proceed, or completely
| swallow them. This is what error handling is about.
| latchkey wrote:
| Gauntlet, the CoffeeScript of golang, seems to have an
| interesting approach:
|
| https://gauntletlang.gitbook.io/docs/advanced-features/try-s...
| klabb3 wrote:
| I love Go, but this is almost farcically hilarious:
|
| > The goal of the proposal process is to reach general consensus
| about the outcome in a timely manner. If proposal review cannot
| identify a general consensus in the discussion of the issue on
| the issue tracker, the usual result is that the proposal is
| declined.
|
| > None of the error handling proposals reached anything close to
| a consensus, so they were all declined.
|
| > Should we proceed at all? We think not.
|
| The disconnect here is of course that everyone has opinions and
| Google being design-by-committee can't make progress on user-
| visible changes. Leaving the verbose error handling is not the
| end of the world, but there's something here missing in the
| process. Don't get me wrong, I love inaction as a default
| decision, but sometimes a decision is better than nothing. It
| reminds me of a groups when you can't decide what to have for
| dinner - the best course of action isn't to not eat at all, it's
| to accept that everyone won't be happy all the time, and take
| ownership of that unhappiness, if necessary, during the brief
| period of time when some people are upset.
|
| I doubt that the best proposals are so horrible for some people
| that they'd hold a grudge and leave Go. IME these stylistic
| preferences are as easily abandoned as they are acquired.
|
| To put another way: imagine if gofmt was launched today. It would
| be absolutely impossible to release through a consensus based
| process. Just tabs vs spaces would be 100 pages on the issue
| tracker of people willing to die on that hill. Yet, how many
| people complain about gofmt now that it's already there? Even the
| biggest bike shedders enjoy it.
| VirusNewbie wrote:
| It's true that Google is "design by committee" and consensus
| driven, but the Go team has been particularly obtuse about
| things, seemingly not understanding which opinions are valid
| and which are confused.
| zaptheimpaler wrote:
| Yeah this bugs me about the decision process too, but Go places
| more importance on backwards compatibility and stability than
| most other languages so it does align with their values. I'm
| not the biggest fan of Go but its nice having a language around
| that favors simplicity and stability.
| derefr wrote:
| To take this analysis another level deeper: what has happened
| here is a classic example of bikeshedding -- and, worse,
| _fostering_ bikeshedding.
|
| _Everyone_ feels equipped to have an opinion about "what
| should be the syntax for an obvious bit of semantics." There's
| no expertise required to form such an opinion. And so there are
| as many opinions on offer as there are Go developers to give
| them.
|
| Limit input on the subject to just e.g. the people capable of
| implementing the feature into the Go compiler, though, and a
| consensus would be reached quickly. Unlike drive-by opinion-
| havers, the language maintainers -- people who have to actually
| continue to work with one-another (i.e. negotiate in an
| indefinite iterated prisoner's dilemma about how other language
| minutiae will work), are much more willing to give ground "this
| time" to just move on and get it working.
|
| (Tangent: this "giving ground to get ground later" is commonly
| called "horse trading", but IMHO that paints it in a too-
| negative light. Horse trading is often the only reason anything
| gets done at all!)
| mseepgood wrote:
| > sometimes a decision is better than nothing
|
| Not in this case. The most popular Go proposal/issue of all
| times was 'leave "if err != nil" alone':
| https://github.com/golang/go/issues?q=is%3Aissue%20%20sort%3...
| thayne wrote:
| If go had started out having different syntax for error
| handling, would these same people request that it change to
| what go currently does? Or is this just resistance to change,
| and wanting to keep what they are used to?
|
| I suspect it is the latter.
| mseepgood wrote:
| The second most popular issue was adding generics. So it's
| probably not a resistance to change.
| bloppe wrote:
| The article seems to admit that a `try` keyword restricted to
| statements / assignments only would combine the best parts and
| alleviate the major concerns of both the `try` and `?`
| proposals. It reads as though the concept has not been
| seriously discussed due simply to exhaustion.
| sho_hn wrote:
| Pro and con. It hinders progress, but it can also arrest the
| "enshittification" of a language that takes place when it
| slowly adds complexity and features and support for whole
| paradigms.
|
| Python does offer a lot more utility for the expert these
| days, but it also went from the maxim of "There is one
| obvious way to do it" to having 5-6 ways to format strings,
| adding more things to be familiar with, causing refactoring
| churn as people chase the latest way to do it, etc.
|
| I'm a C++ developer. I wouldn't want to go back to older
| versions of the language, but it's also _very hard_ to
| recruit any newer programmers into using it willingly, and
| the sheer amount of stuff in it that exists concurrently is a
| big reason why.
| bicarbonato wrote:
| > I love inaction as a default decision
|
| The thing is, inaction is not simply "not taking an action";
| Inaction is taking active action of accepting the current
| solution.
|
| > I doubt that the best proposals are so horrible for some
| people that they'd hold a grudge and leave Go.
|
| But people may leave go if they constantly avoid fixing any of
| problems with the language. The more time passes, the more
| unhappy people become with the language. It will be a death by
| a thousand cuts.
|
| I love go. But their constant denial do fix obvious problems is
| tiring.
| imiric wrote:
| > But people may leave go if they constantly avoid fixing any
| of problems with the language.
|
| For many people the current Go error handling just isn't a
| problem. Some even prefer it over the overengineered
| solutions in other languages. This brutalist simplicity is a
| core design feature I personally enjoy the most with Go, and
| forcing me to use some syntax sugar or new keywords would
| make it less enjoyable to use. I also don't think that
| generics were a net benefit, but at least I'm not forced to
| use them.
|
| Go is a cemented language at this point, warts and all.
| People who don't like it likely won't come around if the
| issues they're so vocal about were fixed. It's not like
| there's a shortage of languages to choose from.
| Mawr wrote:
| > I love inaction as a default decision, but sometimes a
| decision is better than nothing. It reminds me of a groups when
| you can't decide what to have for dinner - the best course of
| action isn't to not eat at all, it's to accept that everyone
| won't be happy all the time, and take ownership of that
| unhappiness, if necessary, during the brief period of time when
| some people are upset.
|
| Invalid comparison - eating one foodstuff or another affects a
| few people for a few hours. Significantly changing a popular
| language affects every single user of it forever.
| mparnisari wrote:
| I have zero complaints about Go's error handling and I'm happy
| with the decision!
| shermantanktop wrote:
| "Not adding extra syntax is in line with one of Go's design
| rules: do not provide multiple ways of doing the same thing"
|
| And so any change to any existing functionality is a breaking
| change that invalidates all code which uses that functionality?
| That design rule smells like hubris on the part of Go's
| designers. It only works if all future changes are extensions and
| never amendments.
| karel-3d wrote:
| Hm, I would agree with this 2 years ago; but now I see how great
| both iterators and generics were despite my initial personal
| objections, so I am thinking we should give some less verbose
| error handling a chance. But alas
| bravesoul2 wrote:
| Slowly reinventing exceptions seems to go against the spirit of
| Go. That is what you read is what you get.
|
| Haskell solves this with the do notation, but the price is
| understanding monads. Go also aims to be easy to understand.
| thayne wrote:
| The proposals aren't reinventing exceptions. They are just
| making more ergonomic syntax for doing the same thing go error
| handling does today.
| bravesoul2 wrote:
| You can argue that is what an exception is. A more ergonomic
| way to deal with errors that handles propagation for you.
| JyB wrote:
| This is such a great move. Faith restored in the language after
| the generics debacle.
| agluszak wrote:
| > generics debacle
|
| Why debacle?
| sedatk wrote:
| I'm so happy that Rust sorted this out way early in its lifetime.
| Error handling is so nice there.
| tick_tock_tick wrote:
| Rust is basically someone looking at ML languages and thinking
| what if we did that but just worse in every way.
| Neku42 wrote:
| Even if the decision to close the door on this sucks I think they
| are correct - this is not a syntax problem. Adding sugar will not
| fix fundamental issues w/ Go's error handling.
|
| They need to add/fix like 5-6 different parts of the language to
| even begin addressing this in a meaningful way.
| ivanjermakov wrote:
| No one here mentioned Zig approach there, so I'll share:
| https://ziglang.org/documentation/master/#try
|
| Zig has try syntax that expands to `expr catch |e| return e`,
| neatly solving a common use case of returning an error to the
| caller.
| joaohaas wrote:
| Zig 'error types' are a sum type, while Go ones are just
| values.
|
| And even then this is just the same as the '?' operator Rust
| uses, which is mentioned in the post.
___________________________________________________________________
(page generated 2025-06-03 23:00 UTC)