The Go Errors Proposal

Last week I sorta dove into the proposed interfaces for errors that will probably come out with Go 1.13. This is my experience.

First though, a very quick review. In Go, the error type is a simple builtin interface:

type error interface {
   Error() string
}

Simply put, errors have an Error method that returns a string. That’s it.

A common package that people use for dealing with errors is github.com/pkg/errors. It provides support for stack traces and adding extra context to errors. Here’s how you might use it:

func configure(path string) (config, err) {
   file, err := os.Open(path)
   if err != nil {
      return config{}, errors.Wrap(err, "Couldn't load config")
   }
   ...
}

Later you might want to call this and handle certain errors:

c, err := configure(".config.json")
if inner := errors.Cause(err); os.IsNotExist(inner) {
   c = defaultConfig()
}

The above is nice, but it’s limiting! I honestly feel a little weird decorating my errors with an effectively opaque string, and not being able to look any deeper. The new errors proposal resolves this by allowing errors to be nested but still individually accessible. Here’s how that works:

type ErrConfig struct {
   Name, Namespace string
   inner error
}

func (i *ErrConfig) Error() string {
   return fmt.Sprintf("%s (name=%s namespace=%s)", err, i.Name, i.Namespace)
}

func (i *ErrConfig) Unwrap() error {
   return i.inner
}

The above is a real example of an error related to configuring a resource that has both a name and a namespace. The error might be rendered to the user like:

Duplicate config (name=NumberOfMessagesDeleted namespace=AWS/SQS)

(The rest of the post mentions errors and fmt; pre Go-1.13 you can use golang.org/x/xerrors to get basically all of the same functionality.)

If you wanted to do more than just render the error, you could detect if it’s a duplicate config error like this:

if errors.Is(err, ErrDuplicateConfig) { ... }

That’s much nicer sugar than we had in the past, but we can do more. Here’s some work code refactored to use the new errors.As function:

res, err := e.Client.GetSecretValue(&secretsmanager.GetSecretValueInput{SecretId: e.Entry.Name})
var aerr awserr.Error
if errors.As(err, aerr) {
	switch aerr.Code() {
	case secretsmanager.ErrCodeResourceNotFoundException:
		return nil, nil
	case secretsmanager.ErrCodeDecryptionFailure:
		return nil, nil
	case "AccessDeniedException":
		return nil, nil
	default:
		return nil, err
	}
}

This is neater than the previous if aerr, ok := err.(awserr.Error); ok {, and actually handles the error nesting.

There’s more to the proposal, like stack trace support and localization of errors, but I don’t expect to use that myself any time.


If you are interested in learning Go, this is my recommendation:

If you don’t already know Go, you should definitely check out The Go Programming Language. It’s not just a great Go book but a great programming book in general with a generous dollop of concurrency.

Another book to consider learning Go with is Go Programming Blueprints. It has a nearly interactive style where you write code, see it get syntax errors (or whatever,) fix it, and iterate. A useful book that shows that you don’t have to get all of your programs perfectly working on the first compile.

Posted Wed, May 8, 2019

If you're interested in being notified when new posts are published, you can subscribe here; you'll get an email once a week at the most.