I had already started playing with my package for Go’s error handling when I read Russ Cox’s problem overview. It resonated: err2 In general Go programs have too much code checking errors and not enough code handling them ― Error Handling - Problem Overview I ended up about the same solution as the Go2’s . Not because it was the best, but because it was the only one. try-proposal Building error checking helpers is pretty tricky, especially without macros. The try-proposal me the trust that I’m on the right path―sort of. Even if the try-proposal were accepted, most of my package’s helper functions would be useful; and switching from to would be easy. gave err2.Try() try() On the other hand, if the proposal would not be accepted, which , something else might be coming, which would still be in align with the package. For example, generics would help but not solve the actual problem: happened err2 A: Implementing try requires the ability to return from the function enclosing the try call. Absent such a “super return” statement, try cannot be implemented in Go even if there were generic functions. ― try-proposal You might ask, why bother? Why not accept how Go’s error handling is currently working? The short answer: because so little was missing. Error Propagation We like to talk that Go has explicit error propagation. However, I agree with Swift authors: I’ve heard people talk about explicit vs. implicit propagation. I’m not going to use those terms, because they’re not helpful: there are at least three different things about error-handling that can be more or less explicit, and some of the other dimensions are equally important. ― Swift Error Handling Rationale and Proposal We should speak about automatic versus manual error propagation. But does it mean that automatic is always implicit and the other way around? Do we think that garbage collection is a bad thing, or RAII is a bad thing? Of course not, they are both automatic and pretty implicit, but very important. Garbage collection is perhaps the best example of simplicity hiding complexity. - Rob Pike Simplicity is Complicated The most common use case is to . Go offers a smart way to produce annotated error messages in the : propagate an error to an end-user K&D style genesis: crashed: no parachute: G-switch failed: bad relay orientation The only thing you have to do is to annotate errors with during the recursive error returns. fmt.Errorf dir, err := ioutil.TempDir( , ) err != { fmt.Errorf( , err) } "" "scratch" if nil return "failed to create temp dir: %v" Indeed, that is an elegant way to build decent error messages. But soon you start to wonder that I don’t want to repeat myself. I need a helper package. Then you remember: Use the language to simplify your error handling ― - Rob Pike Errors are Values Manual Error Propagation Below is the function from the . It shows how we usually handle errors in Go. CopyFile Error Handling ― Problem Overview { r, err := os.Open(src) err != { fmt.Errorf( , src, dst, err) } r.Close() w, err := os.Create(dst) err != { fmt.Errorf( , src, dst, err) } _, err := io.Copy(w, r); err != {. w.Close() os.Remove(dst) fmt.Errorf( , src, dst, err) } err := w.Close(); err != { os.Remove(dst) fmt.Errorf( , src, dst, err) } } func CopyFile (src, dst ) string error // .0: # entry if nil return "copy %s %s: %v" // .1: if.then defer // .2: if.done if nil return "copy %s %s: %v" // .4: if.then if nil // .5: if.done // .7: if.then return "copy %s %s: %v" if nil // .8: if.done // .10: if.then return "copy %s %s: %v" return nil // .11: if.done The (CFG) below is generated from with Go’s and rendered as presentation. I have made a to print CFG of the named function from the given package(s). control-flow graph CopyFile analysis package DOT helper tool Automatic Error Propagation Below is the function implemented with help of package: CopyFile2 err2 { err2.Returnf(&err, , src, dst) r := err2.File.Try(os.Open(src)) r.Close() w := err2.File.Try(os.Create(dst)) err2.Handle(&err, { os.Remove(dst) }) w.Close() err2.Try(io.Copy(w, r)) } func CopyFile2 (src, dst ) string (err error) defer "copy %s %s" defer defer func () defer return nil This is the control-flow graph of the . CopyFile2 Is this fair? I think it is. That is how I see it my self when I’m reading these two code blocks. Things get nastier when some of the if-statements are an essential part of the algorithm, i.e. a . It’s hard to find these decision points when you are skimming over code. happy path Error checks are essential, but because of their nature, they turn to noise without automatic propagation. boosts the , which will help us build better software . Automatic error propagation incremental hacking cycle faster Learnings The most important learning has been that this was all we needed. . End-users get informative error messages, and programmers get stack traces for their mistakes, and debugging is more straightforward. That is achieved by following these three rules: We don’t use any error value wrapping packages , i.e. . For example, . We must with good API design e.g. . Use error values only for factual errors function cannot do what it advertised is not an error io.EOF prevent errors CreateWalletIfNotExist(name string) aka assertions and preconditions. We disagree with the strategy behind . Offensive programming leads faster to correct software. Some parts of the Go’s standard library use offensive strategy as well. Use panics for programming errors, defensive programming Go’s decision not to have assertions . That is pleasant work to do with automatic error propagation without code-smells of manual propagation. Always annotate errors Every function that uses for error-checking must have at least one error handler. Quite often, there isn’t any cleanup to do. Then the error annotation is enough. The package has and for that. err2 err2 err2.Annotate() err2.Returnf() Following code-block illustrates both error annotation and using panics for programming errors. { err2.Annotate( , &err) pipe != && pipe.Out.VerKey() != cs.SignVerKey { s := glog.Error(s) (s) } pipe == { did := ssi.NewDID( , cs.SignVerKey) pipe = &sec.Pipe{Out: did} } ... func (cs *ConnSign) verifySignature (pipe *sec.Pipe) (c *Connection, err error) defer "verify signature" if nil "programming error, key mismatch" panic else if nil // we need a tmp DID for a tmp Pipe "" Conclusion We have used the package almost two years for now with few projects, and this is what we have learned: err2 Deferred functions are natural places for error handlers in Go. ( ) Declarative control structure It’s proven to help us incrementally add better error handling and better error messages. In general, our code is panic-safe which is vital for long-running goroutines. If non-local control flows were needed, we would have a platform ready for that. Understand better what pattern recognition means for programmers: our brain tries to find decision points from the code based on its layout and syntax-highlighting―as pros we use all the help we can get.