This post is adapted from a talk I gave to some coworkers new to go.

I think so much of the success of Go is due to the explicit way errors are handled.

@davecheney in The Zen of Go

You won’t have any a-ha moments about how to think about errors after reading this, but you will know the APIs at your disposal.

Errors are in an interesting place. This proposal, which adds all sorts of new error inspection functionality, is making its way into the standard library (with a placeholder implementation currently available.

A canonical error type, coupled with Go’s other features, makes error handling pleasant but quite different from that in other languages.

Others have done a really good job discussing the philosophy and art of errors in go (see further reading), so I don’t include any of that here.

btw, basically none of the code samples here are my own

what are errors?

the error type is a golang builtin

type error interface {
      Error() string

It is the conventional interface for representing an error condition, with the nil value representing no error.

always remember: errors are values like any other, handle them appropriately

errors in the standard library

import “errors”

this is the support we’ve gotten for errors since go1.13

package errors gives you a few useful utilities for dealing with errors1

remember that interface implementation in go is implicit

the utilities here rely on a few interfaces other than the standard error one described above, namely:

interface {
      Unwrap() error
interface {
      Is(error) bool

func Is(err, target error) bool

it is sometimes useful to know if an error is some known value

this function answers the question: does any error in err’s chain match the target?

matching means one of two things:

  1. that err == target

  2. that, if err has the Is method above, calling target returns true eg err.(interface { Is(error) bool }).Is(target) (don’t actually write code where you don’t check that the assertion worked)

This function recursively calls Unwrap on err, so it will keep going deeper until it finds an example or there are no more errors.

  • Example

    if _, err := os.Open("non-existing"); err != nil {
          if errors.Is(err, os.ErrNotExist) {
              fmt.Println("file does not exist")
          } else {
              fmt.Println("ohnoooo", err)

func As(err error, target interface{}) bool

if Is(err, target), then the contents of target become the first unwrapped error for which this is true

this is useful if you want to extract some information from an underlying error that has been wrapped (by your own code)

  • Example

    this example may seem rather trivial but imagine that instead of os.Open, you have your own complex logic that deep inside eventually calls it

    if _, err := os.Open("non-existing"); err != nil {
          var pathError *os.PathError
          if errors.As(err, &pathError) {
              fmt.Println("Failed at path:", pathError.Path)
          } else {
              fmt.Println("dagnabit", err)
    // prints Failed at path: non-existing

func New(text string) error

just returns a struct with a text field that contains the argument

this is nice in that we can simply make basic errors, but it gives us no other context.

errors.New("your code doesn't work!")

fmt.Errorf is nice in that you can add more context AND it will keep track of the error you pass for use by Unwrap

you have to use the %w formatting directive (and you can only use it once and it can only take an error)

  • Example

    fmt.Errorf("basic error because bad input %q",
          "'); DROP TABLE FAMILY;")
    // unwrappable
    fmt.Errorf("can't read data: %w", err)

func Unwrap(err error) error

calls Unwrap method on err

this is basically how the rest of the methods in this package work

if your errors wrap others and you want the underlying error to be inspectable, implement the method!

type QueryError struct {
    Query string
    Err   error

func (e *QueryError) Unwrap() error { return e.Err }

until we get Go2 errors (or we transition to xerrors), this is the standard for many error operations

here is a subset of the API that is useful to know about (leaving out many things that have already been adopted above)

func New(message string) error

like New from standard library, but with stack information

func WithStack(err error) error

wraps the supplied error with stack information (at the point that this is called)

func Wrap(err error, message string) error

gives human readable context to err while also adding stack information

the future

won’t get into it too much, but this proposal and implementation basically add the functionality in to the language

stack frames!!!

The errors returned from errors.New and fmt.Errorf include a Frame which will be displayed when the error is formatted with additional detail

better formatting

adds new interfaces that allow for finer-grained control of how errors are printed given different formatting directives

This library has the implementations such that people can use them and start transitioning their code over.

Let’s look at the new APIs (many of the functions look very familiar but also add stack information).

func FormatError(f Formatter, s fmt.State, verb rune)

this is totally new!

so you probably won’t ever call this directly, but if your error implements xerrors.Formatter you can get detailed error printing with %+v.

type Formatter interface {

      // FormatError prints the receiver's first error and returns the next error in
      // the error chain, if any.
      FormatError(p Printer) (next error)
func (m *MyError2) FormatError(p xerrors.Printer) error { // implements xerrors.Formatter
      if p.Detail() {
      return nil

see this playground

func Opaque(err error) error

this is cool!

returns an error with the same error formatting as err but that does not match err and cannot be unwrapped

further reading

  1. unfortunately, go uses an unimportable package internal/reflectlite to implement its functionality ↩︎