
Working with Errors in Go 1.13 - kevinconaway
https://blog.golang.org/go1.13-errors
======
denormalfloat
It's poignant to watch Go slowly realize the why other languages have more
powerful, more useful error types. The `error` interface is anemic to the
point of uselessness. Making it unwrappable is step forward, but all that does
is progress it the level of 1995 Java with their "cause" field. It still can't
express suppressed errors (like those from `defer f.Close()`). There is no
standard mechanism to include an optional stack trace. Worst of all, rolling
your own error type and trying to use it everywhere is near impossible. Every
method signature has to use `error` due to lack of covariant return types, and
heaven help you if you return a nil struct pointer, which will be auto
promoted to a non-nil interface. Perhaps in Go 3 they'll get it right.

~~~
jknoepfler
I honestly don't understand where you're coming from.

Rolling your own error library is trivial. We have a great one at work that
works great, prints stack traces, interops with normal error handlers, and
does a lot of custom work for translating errors into external customer-
visible messages and internal developer messages. We use it at every layer of
a 50-60 grpc microservice ecosystem without much headache.

Catching deferred errors is trivial, not sure what you mean there:

defer func () {

    
    
      err := f.Close()
    
      ...
    

}()

Printing stack traces is like 4 lines, I was able to implement ours in 60
minutes of doc skimming. Now it'd be 5 minutes.

I don't understand your last two sentences. It doesn't reflect anything I've
run into in the real world, I don't think.

I have a few gripes about golang, but the minimalist error interface is not
one of them.

~~~
JustARandomGuy
>Rolling your own error library is trivial.

And right there is where you’re going to lose most people.

If it’s is trivial, why isn’t in the standard library? If everyone needs to do
it, why not standardize? I love Go, but i have to agree with grandparent
poster.

~~~
cpuguy83
You can't standardize until people agree. The trivial part isn't the code it's
what is acceptable to everyone.

If people don't agree then it by definition does not belong in the stdlib.

~~~
TheDong
People disagreeing is even more the reason to standardize for things that
matter to the broad ecosystem.

For example, no one likes gofmt (tabs, eww), but everyone likes code across
packages looking the same. And so we use it.

~~~
Vendan
Speak for yourself, Go is a bastion of formatting sanity for me cause they
chose (correctly) to use tabs. Tabs for indentation are the obvious choice to
improve code readability and accessibility for people that want different
indent sizes. I don't need it (usually), but I've met people that prefer
everything from 2, 4 or 8 spaces, and have heard of people wanting everything
from 1 to 8. It also handles far more sanely then spaces for people using
proportional fonts instead of monospace, another common
readability/accessibility tweak.

------
jniedrauer
Stack traces are desperately needed to give errors context, and I was very
disappointed to see that proposal dropped from Go 1.13. At the same time, I'm
hesitant to use xerrors in production code because it's likely to change
rapidly in the future.

I had a situation recently where a fairly complicated API was returning an
HTTP 403 in production. Once the error bubbled down to a level where it was
actually logged, all that was left was "403 Forbidden". I spent days echo
debugging and could not reproduce the problem. Finally, by chance, I happened
on the problem while working on something completely unrelated. With a stack
trace, 3 days of work would have taken 5 minutes. Wrapping would not help in
this situation unless every single error was wrapped with a unique string,
which frankly is ridiculous.

~~~
JulianMorrison
I feel like, in Go, the ideal is to handle the error in situ. If it has to be
passed up, it goes the shortest possible distance before reaching code that
can handle it. This is a difference from exception-throwing languages and so
it changes the importance of a stack trace. You don't need to know where it
broke because it broke _here_. The same approach as in C. You test the result
where you made the call.

~~~
jniedrauer
On the surface this sounds logical. But going back to the case I dealt with
recently, all errors during authentication lead back to an HTTP 403 workflow.
Errors themselves can occur a long distance from any sane central place to
handle them. Unless you pass the http.ResponseWriter all the way up the stack,
the only "in place" handling you could do is to log every error along with a
unique string.

Is this really the best we can do?

    
    
        if err := frobulate(); err != nil {
            log.Printf("Error on line 123: %+v", err)
            return err
        }

~~~
q3k
You example is code smell.

When dealing with error handling in Go (and many other languages), you should
do one _and only one_ of the following:

\- if you have enough context to handle the error (eg. retry, or degrade
gracefully, or perform whatever other application logic to recover, possibly
returning a more friendly error to your caller), do that, possibly logging the
source error and do not return it further up the stack directly - you have now
healed the error condition, and the application code can continue.

\- otherwise, return the error, decorating it with extra context if possible.

Doing both, ie. logging (which is a case of handling the error) and returning
it up the stack leads to extremely chatty and difficult to debug code. Errors
are a fact of life, and your path of handling them should be as well
engineered as the happy life. That's one of the most important things in
writing reliable software.

That's also one of the reasons there's no generic stack unwinding/exceptions
on Go: it forces you to _think_ about error cases and at least reminds you to
handle them gracefully. You might not like this approach of treating
programmers this way, but that's one of the cornerstones of Go.

~~~
xienze
> \- if you have enough context to handle the error (eg. retry, or degrade
> gracefully, or perform whatever other application logic to recover, possibly
> returning a more friendly error to your caller), do that, possibly logging
> the source error and do not return it further up the stack directly - you
> have now healed the error condition, and the application code can continue.

> \- otherwise, return the error, decorating it with extra context if
> possible.

This is precisely how checked exception handling works in Java, and yet
there's no shortage of people complaining about it and praising how Go "makes
you deal with it the way you're supposed to."

> it forces you to _think_ about error cases and at least reminds you to
> handle them gracefully.

So do checked exceptions. And 99% of the time, the solution to an error
popping up in Java is to blindly re-throw it because hey, I'm just a component
buried a couple levels deep in a REST call, what could I possibly do to fix
the problem?

~~~
klodolph
Error handling is hard. Checked exceptions and Go’s error returns are both
good attempts to solve it. They both have plenty of drawbacks but they’re
really different approaches here.

> This is precisely how checked exception handling works in Java, and yet
> there's no shortage of people complaining about it and praising how Go
> "makes you deal with it the way you're supposed to."

Just because people complain about Java’s checked exceptions does’t mean that
their complaints are correct or that they somehow apply to Go’s errors.

The main complaints I hear about checked exceptions are that they force you to
expose the types of all possible exceptions in the method signature. This
creates unexpected API churn and design difficulties, because it’s difficult
to predict which exception types you would want to expose from a particular
interface.

You also have to choose which errors are programming errors and which are not
when you declare the error type. This turned out to cause problems.
NumberFormatException is an example.

> So do checked exceptions. And 99% of the time, the solution to an error
> popping up in Java is to blindly re-throw it because hey, I'm just a
> component buried a couple levels deep in a REST call, what could I possibly
> do to fix the problem?

In Go, blindly returning an error is done much less than 99% of the time, at
least in the code bases I’ve worked on or read.

------
haasted
I love Go, but error handling feels like hand-carried, checked exceptions. And
now they figured adding a “cause” field is practical. I’m getting deja-vu with
Java exceptions 15-20 years ago.

~~~
_ph_
I consider checked exceptions in Java quite a nightmare. You have to specify
the classes of exception you might throw as part of your function signature
and in all methods which might propagate. This is quite a buerocratic monster,
which requires quite a bit of refactoring if your exception signature changes.
Alternatively, all methods in the chain tend to catch all and then rethrow
others.

The described changes could have been added by anyone in earlier Go versions.
These are not things which can be only implemented by the language or compiler
implementor, these are just APIs, which are standardized.

~~~
jayd16
You could just use throws Exception instead and check the error like Go does.
Its a code smell in Java but so is a million exceptions in your method
signature. Most libraries and apis will use a base exception type so keep this
to a minimum.

------
munmaek
The way Go handled errors is why I made the decision to go with rust as my
hobby/personal-open-source-projects language. It was just extremely tedious,
despite Go being nice overall.

I love how error handling is done in Rust. Yes, implementing the From<T> trait
can get verbose, but wrapper libraries like Failure or Snafu exist now as
well.

Being able to propagate errors down the line seamlessly with the ? operator
and Result<T, E> type is nothing short of phenomenal. It looks like the next
version of Rust will seamlessly allow using ? on None types as well.

~~~
richardwhiuk
Look at the failure crate.

For lots of errors, just treating them as a generic failure makes code a lot
better.

~~~
jedisct1
Which is supposed to be replaced by
[https://github.com/withoutboats/fehler](https://github.com/withoutboats/fehler)

~~~
munmaek
Fehler is highly opinionated and I'm not sure it's worth it. IIRC the author
posted on the /r/rust subreddit and was unable to take criticism at all.

I don't like having to put decorators on functions and manually throw!()'ing.
I would rather be explicit about returning Ok(T) or Err(E).

------
BossingAround
_Go’s treatment of errors as values has served us well over the last decade._

Has it? As a Java developer who needed to write something in Go, this has been
quite a frustration for me, personally. In Java, when I get a 100-line
stacktrace error, my IDE analyzes it for me and I can click through the code
and follow what's happening. In Go, I get a string from the developer. Hmm...

~~~
JulianMorrison
That's bad Go design. An API where the particular nature of the error matters,
should declare error constants and use the "Is" mechanism. You shouldn't be
unpicking the internals with an IDE, you should be getting something you can
work with, right there.

~~~
aikah
> That's bad Go design. An API where the particular nature of the error
> matters, should declare error constants and use the "Is" mechanism. You
> shouldn't be unpicking the internals with an IDE, you should be getting
> something you can work with, right there.

AKA roll your own stack trace library. That's what "errors" package and co
are. That's not "bad God design". It's just not Go's problem, as deemed by Go
designers.

Thus criticizing that trade-off is fair, like any trade-offs.

~~~
apta
So we're back to C where each project rolls out its own string implementation.
This really shows the mentality of the golang authors.

~~~
aikah
I mean at that point if you are still using Go, then you agree with whatever
Golang authors tell you.

Most people who were sceptical about Go or were vocal about trying to improve
the language moved on to something else, because they faced a wall of
contempt. The whole Go 2 stunt is more an "idea parking lot" than anything
else.

------
baby
Oh I wish Golang had something similar to Rust's Options and Results. This
`nil` value is the source of many crashes in applications I've audited (due to
applications forgetting to verify if the value is `nil` before acting on it).

Maybe something like this could be supported natively in Golang:

    
    
        func thing(arg bool) Error.Result(bool, error) {
            if arg {
                return Error.Ok(false)
            }
            return Error.Error(fmt.Errof("nope"))
        }
    
        func main() {
    
            if thing() == true {} // doesn't compile
            
            match thing(false) {
                Error.Ok(value) => {
                    fmt.Println(value)
                },
                Error.Error(err) => {
                    log.Panic(err)
                }
            }
        }
    

just for fun I wrote an ugly PoC for bool options:
[https://github.com/mimoo/Bool](https://github.com/mimoo/Bool)

~~~
omegabravo
similar to
[https://golang.org/pkg/database/sql/#NullBool](https://golang.org/pkg/database/sql/#NullBool)

~~~
baby
this has a Value() getter though, but at least you would probably not make the
mistake of not checking the error here.

------
malisper
Can someone help me with what seems to be a basic problem? How are you
supposed to get stack traces for errors in Go? You can wrap an error every
time you return one, but this omits useful information like the line numbers.
Not to mention, a significant amount of the code you are writing is going only
to providing helpful error messages.

Alternatively, it looks like there are libraries out there[0] that will
include stack traces for you. It seems weird to me that it's necessary to use
an external library to get what seems like it should be built into the
language.

Can anyone enlighten me?

Edit: Forgot to include the reference:

[0] [https://github.com/pkg/errors](https://github.com/pkg/errors)

~~~
jniedrauer
As far as I'm aware, there are only two native ways to make an error unique
enough to trace: Wrapping it with a unique string, or throwing a panic. You
might be able to do something clever with panics by throwing them and then
catching them to resume the thread, but I've never seen this done outside a
few places in the Go standard library.

~~~
sjtindell
IIRC this is the suggested way to get a stacktrace. Panic and either recover
or fail.

~~~
jniedrauer
What? You're gonna need to link a source for that, because everything I've
ever read is that panics are for catastrophic failure and should not be used
for routine error handling.

Rob Pike has this to say:

Our proposal instead ties the handling to a function - a dying function - and
thereby, deliberately, makes it harder to use. We want you think of panics as,
well, panics! They are rare events that very few functions should ever need to
think about. If you want to protect your code, one or two recover calls should
do it for the whole program. If you're already worrying about discriminating
different kinds of panics, you've lost sight of the ball.

~~~
BlueDingo
Unless edited, I don't think that's what GP said. Panics are the way to get
stacktraces, not the way to routinely handle errors.

Your top level comment about 403 errors made me assume you did not know about
panic. Obviously your particular situation may have prevented it but I've
always built Go programs from source (not linking binaries) which means I
could always panic whenever I needed a stack trace.

~~~
omegabravo
Panics provide stacktraces, but are not the way to get stack traces.
runtime/debug has the API to generate stacktraces.

github.com/pkg/errors is the ubiquitous library for seamlessly including
stacktraces in errors

------
gregwebs
Great to see this getting standardized!

I maintain an error package that lets users use structured types for errors
and error codes [1]. It is critical to be able to wrap errors without
information loss. I used one standard, a Causer interface, but I will be
switching to Unwrap.

I see complaints about stacktraces here in the comments. I recommend always
using pkg/errors or some error package with stack traces.

[1] [https://github.com/pingcap/errcode](https://github.com/pingcap/errcode)

------
ivanjaros
After years of using Go, I came to realize that the ONLY people that complain
about errors in Go are those who actually do not write any Go code and those
who write Go code less than a year(ie. juniors). There is absolutely nothing
wrong with errors in Go and the creators got it right form the start. Nobody
is forcing you to use errors. You can use booleans if you want. You can
panic(throw exception) if that is what you wish. To each its own. If you want
more syntactic sugar, move to Java, it has plenty of it for you.

~~~
frou_dh
Do have have experience using a language with sum types and exhaustive case
analysis?

------
hackworks
Almost every language supporting inheritance suffers from lack of ability to
check if an error is part of the derivation chain. I have implemented this
using expensive dynamic cast in C++ and instanceof in Java.

Finally Go gets it ahead of the other languages!

~~~
apta
`instanceof` in Java can be surprisingly fast. Plus, it seems that errors.Is
and As rely on reflection, which is slow in golang.

------
guessmyname
Check the original proposal [1] and discussion [2] as well as the unofficial
package [3][4].

[1]
[https://go.googlesource.com/proposal/+/master/design/29934-e...](https://go.googlesource.com/proposal/+/master/design/29934-error-
values.md)

[2]
[https://github.com/golang/go/issues/29934](https://github.com/golang/go/issues/29934)

[3] [https://github.com/pkg/errors](https://github.com/pkg/errors)

[4]
[https://github.com/pkg/errors/tree/c978824](https://github.com/pkg/errors/tree/c978824)

