
Go 1.13: xerrors - henridf
https://crawshaw.io/blog/xerrors
======
nickcw
This (it looks like to me) is an attempt to pull in the best bits of Dave
Cheney's errors package (which I love) into the standard library:
[https://github.com/pkg/errors](https://github.com/pkg/errors)

Standardising error unwrapping is a great idea IMHO and I think that this has
a lot of merit.

I don't like the `fmt.Errorf("more description: %w", err)` though for several
reasons.

Firstly it is a lot more opaque than Dave Cheney's original mechanism
errors.Wrap(err, "more description"). You've got to check the format string
for a `%w` to see if it is wrapping an error or not.

Secondly why is this really important functionality in `fmt` and not in
`error`?

And finally we've been encouraged to write `fmt.Errorf("more description: %v",
err)` (note `%v` not `%w`), so I think there will be a lot of unwrapped
errors.

...

I'm not sure enough has been thought about the backwards incompatibility. With
rclone I try to maintain compatibility with the current go release and a few
previous ones so that rclone can remain running with distro go versions and
gccgo both of which are a bit behind. For something as common as error
handling this will cause lots of libraries to suddenly be no longer usable
with anything less than go1.13.

IMHO I think this would be better staying as a non standard library package
for the time being while more kinks are worked out.

~~~
insertnickname
> _Secondly why is this really important functionality in `fmt` and not in
> `error`?_

`fmt` already depends on `errors`, and Go does not allow cyclical
dependencies, so `errors` would have to reimplement string formatting.

~~~
masklinn
Or they could move the machinery into a shared module.

Which already exists incidentally, internal/errinternal was added so
errors.New and fmt.Errorf could return the same type.

It does make errors pull the entire formatting machinery, but chances are
you're probably using string formatting long before errors so meh…

------
trpc
It seems like golang designers are totally isolated from what's been happening
in the last 30 years in languages design. They still insist on their weird way
of error handling just like they were stubborn for years and years on the lack
of package management and eventually a very weird and rudimentary way of it.
It's sad because I use this language extensively but its weirdly mediocre
design is totally unfathomable. It's like they are very stubborn to do
anything but the right thing.

~~~
oppositelock
I've been using Go heavily over the last five years, starting with 1.2.
Whatever warts the language has in its design, the error handling being one,
it's still an excellent language for getting work done quickly, and correctly.
I'm far more productive in it than Java, C++, or Python, and I'm old enough to
have done lots of Pascal, Lisp, and more scripting dialects than I can count.

Having been writing software over the 30 years that you mention, it's clear to
me that Go has not been isolated from language design, it's just made a choice
to stay simple. Java, C++ and Python have exceptions to keep you from
littering your code with error handling, and code with exceptions is hard to
read. The line of code you are looking at any point in time may instantly jump
to another place, effectively, having "goto"'s evil cousin, the "comefrom".
Somewhere, in your code, is a "comefrom" statement which runs immediately
after the statement you are looking at, you're not aware of its existence. My
Java/Python for production code ends up looking a lot like Go in terms of
error handling, because you have the most context about an error at the point
where it happens, so that is the best place to deal with it. I believe that Go
1.x will have the staying power of C.

~~~
nicoburns
> Java, C++ and Python have exceptions to keep you from littering your code
> with error handling, and code with exceptions is hard to read. The line of
> code you are looking at any point in time may instantly jump to another
> place, effectively,

Rust and Swift both manage to make error handling easy while keeping this nice
"errors are just values" property. Go could easily have added Rust-like or
Swift-like error handling backwards compatibly with their existing idioms
without falling back on weird special cases of format strings, but have chosen
not too.

This does _not_ make the language simple (even though in other ways it is).

~~~
lugg
How is rust error handling better than gos?

~~~
TheDong
Okay, I'll bite.

It's better in many ways.

Firstly, rust has sum types, so it's possible to have exhaustive matches and
know you've handled every case. This isn't possible in go. For comparison:

    
    
        // go
        val, err := doSomething()
        switch err.(type) {
        case *SomeErrorType:
          // handle
        default:
          panic("inexhaustive error check (at runtime, only if that error type is hit)")
        }
        
        // rust
        let (val, err) = do_something()
        match err {
          Err::SomeErrType(inner) => { /* handle */ },
        }
        // won't compile if the match isn't exhaustive
    

Note as well that you have to return the error interface, not some more
specific type, in go because of the "nil struct is not a nil interface"
gotcha. Juggling around structs that are returned as errors is basically
impossible to do safely, so everyone returns the error interface. This is
another way the language causes error handling to be worse.

Next, generics in rust allow for nicer chaining of computation in the presence
of errors. Let me show you two examples. Again, the go and rust code is as
identical as I can make it:

    
    
        // go
        val1, err := computation1()
        if err != nil {
          return nil, err
        }
        val2, err := computation2(val1)
        if err != nil {
          return nil, err
        }
        return computation3(val2)
    
        // equivalent rust
        computation1().and_then(computation2).and_then(computation3)
        
        // also equivalent rust
        let val1 = computation1()?;
        let val2 = computation2(val1)?;
        computation3(val2)
    

The ability to have a generic 'Result<T, E>' type to chain computation allows
for code to be more readable, while still having all the benefits of errors
being values.

The ability to have a generic 'Option<T>' instead of 'nil' also is very
helpful, but enough has already been written about null pointers that I don't
wish to rehash it here.

Finally, in practice, rust's higher level features (namely macros) allows
libraries to create very powerful developer abstractions around errors, like
those offered in the 'failures' crate, all without having any runtime
overhead.

In practice, all of these things also combine to result in libraries offering
better error types and allowing callers to handle errors well.

------
networkimprov
From what I have seen, the Go 2 Error Values plan[1] has not received enough
exposure to generate the level of feedback necessary to support a go/no-go
decision re a major change in 1.13 -- one we cannot opt-out of, at that.

I suspect that the overwhelming majority of Go developers has no idea this is
in the works. It was covered once on the blog last August in the Draft Design
summary, when there wasn't any code behind it. It was not mentioned in Go 2
Here We Come[2], nor at the start of the below golang-dev thread. It was
mentioned on golang-dev when I posted a link to the issue tracker in late
January, but my posts would see a fraction of the attention vs those by Rob,
Robert, Russ, Ian, et al.

There are outstanding issues with the current draft, specifically its
performance[3] and API[4].

If it lands in 1.13, please give it Experimental status, with a build or env
flag to disable changes to existing APIs, and perhaps a way to re-enable them
on a per-function or per-package basis.

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

[2] [https://blog.golang.org/go2-here-we-
come](https://blog.golang.org/go2-here-we-come)

[3]
[https://github.com/golang/go/issues/29934#issuecomment-48650...](https://github.com/golang/go/issues/29934#issuecomment-486503822)

[4]
[https://github.com/golang/go/issues/29934#issuecomment-48350...](https://github.com/golang/go/issues/29934#issuecomment-483509242)

.

[Originally posted on golang-dev, in response to "Last call for feedback on Go
1.13..."]

[https://groups.google.com/d/topic/golang-
dev/jPY0RYXSvCU/dis...](https://groups.google.com/d/topic/golang-
dev/jPY0RYXSvCU/discussion)

------
binwiederhier
As others have stated, this seems incredibly odd to me:

> If the last argument is an error and the format string ends with ": %w", ...

This seems like a magic-string kinda hack to me. I like the idea of wrapping
errors so that you keep the stack and full context, especially since you may
need additional structured data from all errors (e.g. DB error, access error,
...) to produce user facing messages, so IMHO the wrapping should be more
explicit than just %w.

~~~
jerf
It is. It's a backwards-compatibility magic string hack. I would suggest new
code uses the new, formal ways of obtaining the same result.

(I'm not defending it so much as explaining it. I'm not sure how I feel about
it myself.)

~~~
masklinn
Backwards compatibility with what? You have to change the code to do anything
new, that change could be using a different function entirely.

~~~
jerf
See my apology below. I was wrong.

------
albeebe1
Speaking of errors, here's how i approach writing a good error message.

Fill in the blank "Well basically what happened is ___________"

For example i've found this has helped me to go from "Invalid phone number" to
"The phone number needs to be 10 digits"

~~~
spullara
The biggest issue I have with most error messages is that they say things like
you have here but don't reflect back the data that caused the error, i.e. show
me the phone number you think I sent you.

~~~
mattnewton
I think this context is really hard to carry around, and if you aren’t extra
super careful you can end up leaking passwords and other secret bits of memory
here to logs. Better to just give me a place to put a debugger breakpoint IMO.

------
admax88q
Go error handling. Slowly re-inventing Java's exceptions one use case at a
time.

~~~
smilekzs
I agree with the feeling, but I also see this being a debate between an opt-in
approach (choose to use an error value with gradually more information
enclosed) vs opt-out (throw an Exception, a la C#/Java, that by default stores
everything you may or may not need, then maybe figure out a way to stop
storing what you don't need). The use cases are not going to magically go away
though, which explains why we need the solution to be somewhere in the middle.

~~~
admax88q
> then maybe figure out a way to stop storing what you don't need

I'm not sure I understand why this is something you need to do. "Figure out a
way to stop storing what you don't need." Who cares if you don't need it, or
at least don't need it now, storage is cheap.

~~~
apta
Exactly. Plus, code that uses exceptions but does not encounter any throws
should be faster than code that uses error checks. In the latter, there's
always a cost even if there aren't any errors returned. However, the former
can optimize for the (hopefully more common path) of no exceptions being
thrown and avoid checks altogether.

------
spyspy
This seems way better than the other proposals. pkg/errors solved every
complaint I had about errors years ago.

------
acroback
Not sure why people are bashing Go because of it's choices.

Looking forward to shit storm once they introduce generics in near future. I
am sure people will still complain that this is not how parametric
polymorphism should be implemented.

------
lenkite
I think this is a positive development and should be cheered by Go
programmers. Slowly but surely they progress on the road to implementing
Exceptions. ( _ducks and runs_ like a scaredy cat to avoid the wrath of Go
Fans)

------
teabee89
What I'm really looking for is being able to debug any production Go binary by
inspecting the stack trace behind an error. Could be gated by an environment
variable.

