TTuck

In Go it's idiomatic to communicate errors via an explicit, separate return value. This contrasts with the exceptions used in languages like Java and Python. Go's approach makes it easy to see which functions return errors and to handle them using the same constructs used for other tasks.

package main

import (
	"errors"
	"fmt"
	"os"
)

By convention, errors are the last return value and have type error, a built-in interface.

func f(arg int) (int, error) {
	if arg == 42 {

errors.New constructs a basic error value with the given message.

		return -1, errors.New("can't work with 42")
	}

A nil value in the error position indicates that there was no error.

	return arg + 3, nil
}

A sentinel error is a predeclared variable that signifies a specific error condition.

var ErrOutOfTea = fmt.Errorf("no more tea available")
var ErrPower = fmt.Errorf("can't boil water")

func makeTea(arg int) error {
	if arg == 2 {
		return ErrOutOfTea
	} else if arg == 4 {

We can wrap errors with higher-level errors to add context. The simplest way is the %w verb in fmt.Errorf. Wrapped errors form a chain: A wraps B, which wraps C, and so on.

		return fmt.Errorf("making tea: %w", ErrPower)
	}
	return nil
}

func main() {
	for _, i := range []int{7, 42} {

It's common to use an inline error check in the if line.

		if r, e := f(i); e != nil {
			fmt.Println("f failed:", e)
		} else {
			fmt.Println("f worked:", r)
		}
	}

	for i := range 5 {
		if err := makeTea(i); err != nil {

errors.Is checks that a given error, or any error in its chain, matches a specific error value. This is especially useful with wrapped or nested errors, letting you identify specific error types or sentinel errors in a chain.

			if errors.Is(err, ErrOutOfTea) {
				fmt.Println("We should buy new tea!")
			} else if errors.Is(err, ErrPower) {
				fmt.Println("Now it is dark.")
			} else {
				fmt.Printf("unknown error: %s\n", err)
			}
			continue
		}

		fmt.Println("Tea is ready!")
	}

The standard library wraps errors the same way: the message carries the context and errors.Is still finds the cause.

	_, err := os.Open("/no/such/file")
	fmt.Println(err)
	fmt.Println("not exist:", errors.Is(err, os.ErrNotExist))

errors.Join combines several errors into one.

	joined := errors.Join(errors.New("name is required"), errors.New("age must be positive"))
	fmt.Println(joined)
}
$ go run ./27_errors
f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.
open /no/such/file: no such file or directory
not exist: true
name is required
age must be positive