Learn Go← Dashboard

The error interface, wrapping with %w, errors.Is / errors.As.

The error Interface

Go doesn't have exceptions. Instead, functions that can fail return an extra error value, and the caller checks it. This sounds tedious but makes failure explicit and inescapable in your code.

The interface

The built-in error is just:

type error interface {
    Error() string
}

Any type with a Error() string method is an error.

Creating errors

import "errors"

err := errors.New("not found")
err := fmt.Errorf("user %d not found", id)   // formatted

errors.New is the simplest. fmt.Errorf is what you reach for when the message needs values from variables.

The classic check-and-return

go playground
Loading...

Custom error types

Sometimes you need an error that carries extra data — say, a status code, or a field name. Define a struct with an Error() method:

go playground
Loading...

A caller can type-assert this back into *ValidationError to access Field — or do that with errors.As, which we'll see next.

The golden rule

Don't ignore errors. Either handle them, or wrap and return them.

The pattern if err != nil { return ... } shows up everywhere. Embrace the noise — each line is the language pointing at a place where something can break.

Sentinel errors

A sentinel is a package-level error you compare against. The standard library exports plenty:

import "io"

if errors.Is(err, io.EOF) {     // end of stream
    break
}

You can define your own:

package shop

var ErrOutOfStock = errors.New("out of stock")

Callers then check with errors.Is(err, shop.ErrOutOfStock). We cover errors.Is next lesson.

What does the built-in `error` type look like?