
An Open Letter to the Go Team About Try - ingve
https://www.ardanlabs.com/blog/2019/07/an-open-letter-to-the-go-team-about-try.html
======
rsc
FWIW, I wrote a short response here:
[https://twitter.com/_rsc/status/1146128393542492160](https://twitter.com/_rsc/status/1146128393542492160)

I want to flag especially that there is not, nor has there ever been, a plan
to "push this through".

People consistently say error handling is problematic for them in Go. Overall
I think people spend more time writing error checks than they should, and at
the same time there are still too many mis-handled errors. We would like to
see if we can find a way to make it both less work and more correct. Maybe we
can, maybe we can't. The process is slow and needs to backtrack out of
formerly-promising dead ends, and that's OK.

There's a very large amount of conversation right now around try, and that's
good. Like I said in the tweets, community-wide discussions like this one are
open source at its best.

~~~
pfarnsworth
I think "try" is a terrible name, because it overloads try/catch which is very
common usage these days but it's nothing like that. It's basically a error
checking macro, so give it a new name.

~~~
Sphax
Rust had the try! macro before which is similar, the name is fine I think.

------
munificent
_> Rob Pike gave a talk last year at the Go Sydney meetup where he talked
about the Go 2 changes. In his talk, he suggested that the use of if err !=
nil in code bases is nowhere near as common as the vocal minority suggest._

This page shows the most common words found in a large corpus of open source
Go code on GitHub:

[https://anvaka.github.io/common-
words/#?lang=go](https://anvaka.github.io/common-words/#?lang=go)

The top four most common words are "err", "if", "return", "nil". These are
each more common than "func" and "string" _combined_. It's hard to square
Pike's comment with that evidence.

I don't know if the try proposal is a good one or not, but I think it's pretty
clear that Go's original approach to error handling is quite verbose. Whether
it _should_ be that verbose is an interesting question, but personally I think
making the user repeat that _exact same boilerplate_ every time adds little
value. If you want code that can fail to be visible, at least make the
boilerplate shorter, which I guess the "try" proposal accomplishes.

~~~
apta
Just goes to show how out of touch with established programming language
research and practice the golang authors are. Now they're re-inventing
exceptions, but badly.

It seems a lot of things in golang were done just for the sake of being
different, with nothing to back it up.

~~~
Cthulhu_
> It seems a lot of things in golang were done just for the sake of being
> different, with nothing to back it up.

Take an afternoon to go through the Go FAQ [0] (they answer why they don't
have exceptions), design documents [1], including this fairly recent one [2]
about error handling - they explicitly discuss exceptions and the pros / cons
- and that's just what ended up in the design documents / FAQ, there's been at
least ten years worth of discussion gone into that beforehand. Do some
googling before making accusations like that please.

[0]:
[https://golang.org/doc/faq#exceptions](https://golang.org/doc/faq#exceptions)
[1]:
[https://github.com/golang/go/wiki/DesignDocuments](https://github.com/golang/go/wiki/DesignDocuments),
[https://github.com/golang/proposal](https://github.com/golang/proposal) [2]:
[https://go.googlesource.com/proposal/+/master/design/go2draf...](https://go.googlesource.com/proposal/+/master/design/go2draft-
error-handling-overview.md)

~~~
apta
I'm aware of their rationales, what I'm saying is that they don't make sense
and are dismissive, and have no studies at all to back them up.

It's there in the first link you posted for example, "we believe exceptions
lead to convoluted code". Belief here is not sufficient, it must be
conclusively shown that it's otherwise, especially given that _subjectively
speaking_ it is possible in golang for errors to slip and not be handled at
all (I saw it in real code bases), much much worse than any "convoluted" code
that exceptions may result in (again it's very subjective - the way golang
handles errors results in much longer code that's harder to follow).

------
tech_dreamer
I agree with blog author's sentiments. Go is a bit too imperative but that
really aides in a team environment. Though the Golang's constructs are a bit
bland (especially error handling) it actively prevents code to be too
intelligent there by aiding readability I am able to work on the code I wrote
2 years ago with little difficulty in Golang and I can't make the same claim
for other `leading` languages (though I assume full responsibility for what
happened)

Edit: grammer and spelling

------
guessmyname
Before commenting, please read the detailed design document [1] and see the
discussion summary as of June 6 [2], the summary as of June 10 [3], and
specifically the advice on staying focussed [4]. Your question or suggestion
may have already been answered or made. Thanks — ATT: Robert Griesemer

\---

I like writing _“if err != nil { … }”_ so while I disagree with the proposal
to add the error check function “try()” I will keep an open mind because I do
not have the technical literacy as @griesemer or other people in the Go
community to propose something better. I’ll experiment with “try” a few times
in personal projects, but I can see myself writing “if err != nil” for several
years unless I find a more compelling reason to change, same reason why my
team and I haven’t adopted “go mod” despite having Go v1.12.6 (latest stable
version as of today) in all our production servers.

[1]
[https://github.com/golang/proposal/blob/master/design/32437-...](https://github.com/golang/proposal/blob/master/design/32437-try-
builtin.md)

[2]
[https://github.com/golang/go/issues/32437#issuecomment-49926...](https://github.com/golang/go/issues/32437#issuecomment-499261947)

[3]
[https://github.com/golang/go/issues/32437#issuecomment-50061...](https://github.com/golang/go/issues/32437#issuecomment-500613160)

[4]
[https://github.com/golang/go/issues/32437#issuecomment-50187...](https://github.com/golang/go/issues/32437#issuecomment-501878888)

~~~
someone7x
> I like writing “if err != nil { … }”

That idiom was my biggest turn off in learning Go.

I would be 100x happier with "if not err {...}"

------
ngngngng
Bill helped me with my resume once in the Go slack channel. Great guy. I also
happen to agree wholeheartedly that introducing try would be disastrous to the
mostly consistent codebases Go is currently able to painlessly produce.

Personally I don't want error handling changed at all. It's very repetitive in
it's current state, but clearer than in any other language I've used.

~~~
asdkhadsj
Eh, I feel like Go errors are a mess, personally. Though, I'm not sure how to
solve them. Mind you, this is after ~5 years of use, so it's not from "an
outsider" \- I hope, at least lol.

Most of blame for the mess is that the error interface throws away meaningful
information. Then the caller is left mentally checking how to handle the
error. Do I value check? Type cast? String check? Does the library expose a
function to check, or do I do it myself? Can I get stack traces in some cases
without introducing a production cost?

I'm not sure if Go needs a different type of error interface (ie diff
methods), or if it needs Generics to solve this properly in my view. But, I
just know I've hated the experience.

After I switched to Rust it all felt so familiar, and yet so different. I
believe Rust and Go share error patterns. Yet in Rust I'm never left wondering
what an error is, what I should do with it, how I check for specific types.
This is largely due to awesome enum support and of course generics. Does Go
need this feature set to "fix" my complaints with errors? Probably not.. but I
do think it needs something.

Though I will agree, I don't think the `if err != nil` is fundamentally the
problem with Go's errors, at all. Sure, the UX could be improved, but that's
another story all together imo.

~~~
hermitdev
Honest question as a non Go user: how would generics (from a C# perspective)
help? I assume that Go generics would be analogous those in C#.

Edit: spelling, genetics to generics.

~~~
Nullabillity
Rust has a wrapper type `Result<ReturnType, ErrorType>`[0] that essentially
works the same was as Go's `return value, err` idiom, but is safer because the
type is defined so that you can't access the returned value unless you also
handle the error condition, and it can have a convenience syntax for chaining
since it is a dedicated unambiguous type. The compiler also specifically warns
you about unused `Result`s, to avoid unhandled errors, and the API is
structured so that nonsensical states such as `(nil, nil)` or `(value (!=
nil), error (!= nil))` are impossible (unless specifically opted into).

Without generics you would have to either throw away the type information,
reimplement a `Result` type for every value type, or copy/paste the
implementation at each usage site (the common Go pattern).

Go's support for multiple returns itself is also a hack around the lack of
support for tuples (which would be trivial to implement if you had generics).

[0]: [https://doc.rust-lang.org/std/result/enum.Result.html](https://doc.rust-
lang.org/std/result/enum.Result.html)

~~~
mappu
_> a hack around the lack of support for tuples (which would be trivial to
implement if you had generics)_

To a first approximation, the purpose of Go is to stop every codebase from
reinventing its own tuples or Result<> type.

Go does have generics, the Go compiler could add a chaining syntax for
trailing err interfaces (like rust ? / try!) without needing to allow user-
defined generic types.

~~~
mrgriffin
I hope it's not literally to prevent people reinventing these simple data
structures.

The ecosystem exerts a lot of pressure on developers to not reinvent
fundamental things, in Haskell I practically never reach for a StrictTuple or
StrictEither even when I'd like strictness specifically because they are
clunkier and less well integrated with other libraries.

Scala is the only language that comes to mind with this issue (scalaz vs Cats)
and my impression is that even they have mostly converged.

I would say Go has much stronger idioms than either of these languages, and
wouldn't need to be afraid of people inventing their own fundamental types
provided they're included in the standard library.

~~~
majewsky
> Scala is the only language that comes to mind with [the issue of needlessly
> reinvented fundamentals]

C/C++ is another example. There are a plethora of string types mostly tied to
which base set of libraries you're using. If you're using the C++ stdlib, you
have std::string. But if you're using Qt, you're using QByteArray and QString
instead. And probably similar for GLib/GTK.

(Does std::string even have any encoding support yet? When I last used it, it
was just a string of bytes without any encoding awareness.)

------
tgsovlerkhgsel
> biased because only those who had a problem submitted proposals and this
> leaves out all of the developers who don’t consider error handling in Go
> needs to be better

It's probably also biased due to the people who gave up on Go completely
because they saw it as a sufficiently major issue and/or had little faith in
Go ever improving (I can't exactly put a finger on it, but a lot of the
decisions in Go seem to be backed by "... and if you don't like our approach,
you're WRONG").

~~~
mleonhard
> "if you don't like our approach, you're WRONG"

This was also my experience interacting with the Golang team while working at
Google.

------
guidovranken
I don't write a lot of Go but I have fuzzed a reasonable amount of it. One of
the most common types of bugs I encounter are slice out-of-bounds errors. In
Go, this invariably leads to immediate program termination (panic). Especially
parsers of serialized data received over the wire (like protobufs) are
susceptible to slice OOB bugs because the programmer needs to reason about
lengths, offsets and integer wraparounds, and this is often hard to get
exactly right. Due to the combination of parsing untrusted data, the
likelihood of bugs and flat-out crashes if there is a bug, network-facing Go
software is especially prone to very low-effort denial-of-service attacks.

Python and Java got this right: everything throws an exception, including out-
of-bounds access (IndexError, ArrayIndexOutOfBounds), so by wrapping your
whole application in a try/except block you can eliminate a whole class of
otherwise fatal errors.

Unless I missed something, the upcoming change isn't going to alter the
handling of OOB's, which seems like a missed opportunity because a fully-
fledged try/catch mechanism could effectively defuse many bugs.

~~~
tech_dreamer
In Java land checked exceptions are considered to be an anti pattern for
sometime. If I remember correctly one of the major change in Hibernate ORM 3.0
was converting checked exceptions to runtime equivalents (ie, caller is free
to disregard the exception) (this happened 10-12 yrs ago). Coming back to
Golang, you get the same behaviour if you are not checking `error is nil`
condition

~~~
erik_seaberg
When Java finally got generics, it didn't add any support for generic sum
types of checked exceptions.

    
    
      stream.map(f).collect(…)
    

should obviously be able to throw anything f could throw. But instead f is
forced to wrap every checked exception, and so pretty much everyone has given
up and started declaring new exceptions as unchecked.

------
thr23d93
I find the logic the article uses to make its point a bit handwavy, or you
could even say "wrong". If the language specification required the first line
to start FIRST and then every subsequent line in each file start with AND
THEN, so that this:

    
    
       package main
       
       import (
        "fmt"
        "net/http"
       )
       
       func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
         fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
        })
       
        http.ListenAndServe(":80", nil)
       }
    

becomes

    
    
       FIRST package main
       AND THEN 
       AND THEN import (
       AND THEN  "fmt"
       AND THEN  "net/http"
       AND THEN )
       AND THEN 
       AND THEN func main() {
       AND THEN  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
       AND THEN   fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
       AND THEN  })
       AND THEN 
       AND THEN  http.ListenAndServe(":80", nil)
       AND THEN }
    
    

there is a very good chance it would not make it into "the biggest challenge
you personally face using Go today." Because it's not a challenge. It's
stupid, but not a challenge.

So just because the verbose incantation

    
    
       if err != nil {
           return err
       }
    

or other issues, could be thought of as very similar to AND THEN, it doesn't
mean that this is the biggest challenge someone personally faces. it could be
stupid, but you get used to it.

so it depends on the wording of the question. Basically the real question
isn't how big of a pain point it is. It's how much it could be fixed.

my specification for the formatting of the language would be hugely improved
by removing the AND THEN line from each line of the code. It's kind of ugly.

~~~
tgsovlerkhgsel
It would also probably not come up in surveys, because the only people using
the language would be the ones who are OK with it - others would simply take a
look at it, conclude that having a pointless "AND THEN" everywhere is just too
ugly, and move to a language that made better choices.

------
tomohawk
I've heard people complain about the explicit error handling in go, but that
is mostly because they are coming from other languages such as java which have
inferior error handling that lets them write 'neater' code, but which
generally results in less than robust error handling.

Picking on Java, it has checked exceptions, unchecked exceptions, and errors.
It's basically a minefield of complexity leading to undefined behavior and/or
misbehaving error legs. I can't tell you how many times I've seen
InterruptedException mishandled, Throwable (which includes
VirtualMachineError) caught with no appropriate handling (quick - what is the
appropriate handling of a VirtualMachineError?), or exceptions allowed to
wildly propagate.

~~~
kjeetgill
> java which have inferior error handling that lets them write 'neater' code,
> but which generally results in less than robust error handling

Wowza, that's a strong stance.

Look, I'm a fan of Go and Java is definitely aged in a lot of places but this
is one of the most bizarre claims I've seen. Criticism of Java's exceptions
come from both sides, some people who hate unchecked exceptions so "anything
can throw" (same reason C++ camp often avoids exceptions); or that checked
exceptions are stupid since you can't meaningfully fix Interruptions or
IOExceptions so it's verbose and meaningless to force local catches; they
almost always need a rethrow. _Both_ of these criticism are fair because in
the end they both make sense sometimes and thus are misused for one another
occasionally. We'd all like one answer consistently applied but we don't have
it here.

Error codes formalized as a part of an API have their place but I find their
utility is much more niche than people give them credit for. Why should I
decode error codes when a String and a Trace will do? Hint: only when errors
have formal semantics such as within a protocol. How I handle the error is
partially decided by spec.

But to say that in comparison to Go? Go has its normal err mechanism and
panic/recover. err is a huge step up from C style errno/manual checking. In
practice, Go's err is embarrassingly used just like a checked exceptions:
observe and ignore/log or propagate. You end up just unwinding the stack
manually, losing the trace, maybe adding context occasionally, like 70% of the
time. You handle it where it's meant to eventually, sure.

Like everybody does, when it is done right.

panic/recover is just unchecked exceptions all over again anyway. They seem to
be treated as voodoo idiomatically, sure, but they're there.

Go is a great language that brought some really good language runtime features
together in a pretty tidy package. Besides maybe defer, which is pretty clean,
error handling just isn't a part of the language that got the polish that
other parts of the language got on the first pass.

------
apsdsm
This blog pretty well sums up my feelings about the try method. It just
doesn’t really seem necessary. I’m sure it would help in some of my code, but
importantly not _all_ of my code, and if a construct is only available some of
the time, I will inevitably favor the doing the same way _all_ of the time
rather than adding a little syntactic sugar every now and then.

I’d much rather see some kind of genetics support get well integrated, given
that’s a problem I run into fairly often.

~~~
100100010001
Agreed! (Though I’m assuming you meant to type generics. However, I’d love a
library to alter genetics)

~~~
apsdsm
Hahah this is what I get for typing on my phone.

------
zmj
Go errors prioritize explicitness, in declaration of what functions might
fail, and in handling of those failures.

It seems like a mistake to add a language feature that works against explicit
handling. At minimum, the endorsement of returning the raw error reduces
clarity on what "good error handling code" should look like.

~~~
marcus_holmes
agree totally.

I'd prefer to see an official errors.Wrap function instead of "try", for the
reasons you cite.

(shout out to Dave Cheney's excellent "pkg/errors" implementation of error
wrapping)

------
Dylan16807
> effort to understand exactly what those 5% of Go developers meant when they
> said they wanted improved error handling

> If you look closely, error handling is not even in the top 3 challenges
> faced by developers, it’s number 5

Nonono, that's not what those numbers mean. 5% said it was their absolute
biggest problem. For all you know it could be the #2 problem of 90%.

------
cfors
This will be legal with the proposed `try` statement [0]:

    
    
      info := try(try(os.Open(file)).Stat())
    

I'm worried about nested versions of this and having to unpack them when
reading code, which at the current moment has a much easier imperative block
structure.

[0]
[https://github.com/golang/proposal/blob/master/design/32437-...](https://github.com/golang/proposal/blob/master/design/32437-try-
builtin.md#properties-of-the-proposed-design)

~~~
pcwalton
I've never seen this come up as an issue when reading Rust, which has the same
feature being proposed for Go (and then later shortened it to just a single
character, ?).

~~~
Dylan16807
? is a big improvement over try specifically because you _don 't_ have to
unpack nested statements. Everything flows left to right.

------
kstenerud
I was never a fan of try. It's too magical and breaks the flow.

Far better to use something more explicit and controllable:

    
    
        f, err := os.Open(filename) onerr return 0, err
    

which is just syntactic sugar for:

    
    
        f, err := os.Open(filename)
        if err != nil {
            return 0, err
        }
    

also:

    
    
        err := DoSomething() onerr {
            fmt.PrintF("We got error %v\n", err)
            return 0, err
        }

------
ridaj
The way errors are handled in go feels like the people were thinking hard
"let's not repeat the horror of C++ exceptions" and are ok with ugly warts so
long as they're not uglier than exceptions...

It's clear that there's way too much boilerplate in go procedures that deal
with error handling by systematically bailing and passing the failure
upstack...

But the most confusing thing to me about try() is that, although it's shaped
like a function, it actually returns from the current scope on error. It's
dangerous syntactic sugar IMO to make this one function-call-like feature not
behave like an actual function call...

~~~
atombender
Too late, Go already has panic(), which is a magical function that unwinds the
stack.

~~~
ridaj
!

So it's legal to write something like...

    
    
       panic(panic())
    

or

    
    
       a := try(try(f()))
    

...?

~~~
atombender
panic() doesn't have a return value, so no. But try() has.

------
cbhl
I wonder if Go just needs syntactic sugar that's similar to the
RETURN_IF_ERROR that's common in Google C++ code (for example, in protobuf:
[https://github.com/protocolbuffers/protobuf/blob/master/src/...](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/stubs/status_macros.h#L49))

------
alpb
Does anyone have a link to the "try" proposal the author is advocating
against? It's not clearly linked in the article.

~~~
Iggy173
[https://github.com/golang/go/issues/32437](https://github.com/golang/go/issues/32437)

------
foota
Try aka ASSIGN_OR_RETURN

------
nikolay
All the try(), catch(), and onerr proposal are ugly patchworks. All I need is
to be able to skip the != nil and just be able to write: if err { ... }

------
staunch
I'm glad this post (and others) are sounding the alarm.

Go's greatness could very easily be destroyed by a small number of missteps
like this one.

I'm wishing for the absolute rule of the curmudgeony quadrumvirate like the
one that designed Go. This feature-bike-shedding hungry mob is proof of the
problem with democracies.

And when it comes to programming languages (unlike societies) the the trade-
offs for a democracy are not worth the price. Go should (almost) completely
ignore the crowd on design issues. It should have changes made that a small
cabal of experts unanimously agree with.

------
TheChaplain
This proposal really seem to rile up the community, can't it be shelved and
revised until let's say v2.4 or something?

~~~
tgsovlerkhgsel
Of course it can. It can also be shelved indefinitely or not done, but at some
point, it's time to make a decision. Opponents of this would of course prefer
it to be "shelved" instead.

------
apatheticonion
I wouldn't like to see a change to error handling, I would like to see
generics added (in the form that was described in the Sydney talk).

Maybe a stack trace would be nice in the errors, though

------
knodi
Stop fighting it embrace it. Error checking in go is one of my fav part of go.

------
amluto
I am not a Go programmer, but:

The article suggests that try() isn’t solving a big problem. If I were a Go
programmer, I think I would want a bigger change, specifically, a proper sum
type for errors. In Go, the convention is to return a result _and_ an error
despite the fact that essentially every function that can return an error
wants to return a result _or_ an error. This results in complexities in the
proposal about what the result should be when try() causes an error return.
The sensible answer is that there should not be a result.

The way that Rust (and Haskell and many other languages) do so much better
than Go has little to do with fancy ? operators. It’s that the return type
makes sense.

Of course, sum types without genetics would be odd, but I don’t see how this
would stop Go — a special case specifically for “such-and-such type or error”
seems consistent with the Go typing philosophy.

~~~
kjeetgill
I think it's funny that you mention a bigger change but sum-types are 80% of
what Go already has here. While it's true that it solves the ambiguity issue
of representing `Either Result or an Error But Not Both` instead of the
`Result or Error or Both`; that doesn't solve the issue that anyone actually
has, which is that most errors are handled with

    
    
      if err != Nil {
        return err;
      } 
    

_after every other line_ sometimes. It's verbose, manual, and doing the
helpful thing like adding context for the error is repetitive and often
omitted. As I've mentioned elsewhere, you essentially want an exception
system.

Some care still needs to be taken with how other language features interact
with it. You'll probably want a sum-type-y way to catch exceptions as an
expression eventually. What happens if exceptions are thrown from functions in
defers, etc. But unlike more FP languages where error propagation is much
easier in a soup of composed functions, Go is much more Java/C#-like. Most
functions have much more important names and meaning which make stacktraces
much more valuable.

