Go Tutorial · Errors · Lesson 22 of 30

Go Error Handling

error values, wrapping with %w, errors.Is and errors.As, panic and recover.

Errors are values

Functions return an error as their last result; callers check it right away. It's explicit and a little verbose — on purpose:

Example 1
package main

import (
	"fmt"
	"strconv"
)

func parsePort(s string) (int, error) {
	n, err := strconv.Atoi(s)
	if err != nil {
		return 0, fmt.Errorf("invalid port %q", s)
	}
	return n, nil
}

func main() {
	for _, input := range []string{"8080", "eighty"} {
		port, err := parsePort(input)
		if err != nil {
			fmt.Println("error:", err)
			continue
		}
		fmt.Println("port", port)
	}
}

Output

port 8080
error: invalid port "eighty"

Run this on your computer — see “Get Started” for setup.

Wrapping errors

Add context with fmt.Errorf("...: %w", err). errors.Is checks for a specific error anywhere in the chain; errors.As extracts a custom error type:

Example 2
package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("not found")

func findUser(id int) (string, error) {
	return "", fmt.Errorf("find user %d: %w", id, ErrNotFound)
}

type ValidationError struct{ Field string }

func (e *ValidationError) Error() string { return e.Field + " is required" }

func validate(name string) error {
	if name == "" {
		return fmt.Errorf("create user: %w", &ValidationError{Field: "name"})
	}
	return nil
}

func main() {
	_, err := findUser(42)
	fmt.Println(err)
	fmt.Println(errors.Is(err, ErrNotFound))

	err = validate("")
	var ve *ValidationError
	if errors.As(err, &ve) {
		fmt.Println("invalid field:", ve.Field)
	}
}

Output

find user 42: not found
true
invalid field: name

Run this on your computer — see “Get Started” for setup.

panic and recover

panic is for truly unrecoverable situations (programmer bugs), not normal errors. recover inside a deferred function stops a panic — used at boundaries such as HTTP handlers so one bad request doesn't crash the server:

Example 3
package main

import "fmt"

func safeRun(f func()) (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("recovered: %v", r)
		}
	}()
	f()
	return nil
}

func main() {
	fmt.Println(safeRun(func() { fmt.Println("all good") }))
	fmt.Println(safeRun(func() {
		var items []int
		_ = items[3] // index out of range: panics
	}))
}

Output

all good
<nil>
recovered: runtime error: index out of range [3] with length 0

Run this on your computer — see “Get Started” for setup.

Test yourself

Which function checks whether an error chain contains a specific error value?

  1. errors.As
  2. errors.Is
  3. errors.Unwrap
  4. err == target
Show answer

Answer: errors.Is