๐ŸŽ“ A1Learner
๐Ÿ”
0 of 34 lessons completed

Go Error Wrapping

Error wrapping is placing one error inside another error so both messages stay in one value. The outer error adds context about where the failure happened.

One wrap carries both messages

run wraps the error from check with %w. The printed line holds the context and the inner message.

package main

import (
	"errors"
	"fmt"
)

func check(n int) error {
	if n < 10 {
		return errors.New("value too small")
	}
	return nil
}

func run(n int) error {
	err := check(n)
	if err != nil {
		return fmt.Errorf("check %d: %w", n, err)
	}
	return nil
}

func main() {
	fmt.Println(run(4))
}

errors.Is sees through two wraps

The base error sits under two wraps. errors.Is still finds it and misses an unrelated error.

package main

import (
	"errors"
	"fmt"
)

func main() {
	base := errors.New("value too small")
	wrapped := fmt.Errorf("middle: %w", base)
	outer := fmt.Errorf("outer: %w", wrapped)
	fmt.Println(errors.Is(outer, base))
	fmt.Println(errors.Is(outer, errors.New("file missing")))
}

Each wrap adds one layer of context

store makes the first error. load and report each wrap it with one more layer.

package main

import (
	"errors"
	"fmt"
)

func store(n int) error {
	return errors.New("value too small")
}

func load(n int) error {
	return fmt.Errorf("load %d: %w", n, store(n))
}

func report(n int) error {
	return fmt.Errorf("report: %w", load(n))
}

func main() {
	fmt.Println(report(4))
}

Example

package main

import (
	"errors"
	"fmt"
)

func parse(text string) error {
	if text == "" {
		return errors.New("empty text")
	}
	return nil
}

func validate(text string) error {
	err := parse(text)
	if err != nil {
		return fmt.Errorf("validate: %w", err)
	}
	return nil
}

func submit(text string) error {
	err := validate(text)
	if err != nil {
		return fmt.Errorf("submit: %w", err)
	}
	return nil
}

func main() {
	fmt.Println(submit(""))
	fmt.Println(submit("hello"))
}

Key points

  • Use fmt.Errorf with the %w verb to wrap an error.
  • The %w verb keeps the inner error inside the outer error value.
  • The printed line of a wrapped error carries every message in the chain.
  • A chain is the outer error plus each error wrapped inside it.
  • Use errors.Is to test whether a chain holds a given error.
  • errors.Is follows the chain through each wrap to look for its target.
  • An error that is not in the chain gives false from errors.Is.

In this example, parse returns an error for the empty text. validate wraps that error with its own name. submit wraps the result again, so the first print shows all three layers. The second call passes good text, so submit returns nil and the print shows <nil>.

Note

The %v verb prints an error but does not wrap it.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Go Playground

Exercise

Which verb in fmt.Errorf wraps an error so errors.Is can find it?