Lewislbr

Errors in Go

In Go, errors are values and act like any other data type, so they can be function parameters, return values, and so on. At its core, an error is an object that has an Error() string method, thus satisfying the Error interface. Additionally, they can be wrapped with extra information during their usage by formatting them with %w, allowing for error chains that can be used to trace the origin of an error.

When using errors constructed with errors.New, they can be checked for their specific type with errors.Is, which will report whether any error in the chain matches:

var ErrFoo = errors.New("foo")

func main() {
	err := bar()
	if err != nil {
		if errors.Is(err, ErrFoo) {
			fmt.Printf("foo error: %v\n", err)
		} else {
			fmt.Printf("random error: %v\n", err)
		}
	}
}

func bar() error {
	err := foo()
	if err != nil {
		return fmt.Errorf("error in bar: %w", err)
	}

	return nil
}

func foo() error {
	return ErrFoo
}

// Output:
// foo error: error in bar: foo

Errors should not be checked with == as it does not perform unwrapping, and only the first error of the chain will be compared:

var ErrFoo = errors.New("foo")

func main() {
	err := bar()

	fmt.Println("Is error foo with Is?", errors.Is(err, ErrFoo))
	fmt.Println("Is error foo with ==?", err == ErrFoo)
}

func bar() error {
	err := foo()
	if err != nil {
		return fmt.Errorf("error in bar: %w", err)
	}

	return nil
}

func foo() error {
	return ErrFoo
}

// Output:
// Is error foo with Is? true
// Is error foo with ==? false

Errors can also be constructed using custom objects that satisfy the error interface, and can be checked with errors.As, which will check for the first error in the chain that matches, and set it to the custom error value:

type ErrorA struct {
	Err     error
	Message string
}

func (c *ErrorA) Error() string {
	return c.Message
}

type ErrorB struct {
	Err     error
	Message string
}

func (c *ErrorB) Error() string {
	return c.Message
}

var ErrFoo = errors.New("foo")

func main() {
	errA := &ErrorA{}
	errB := &ErrorB{}
	err := bar()

	fmt.Println("Is error A?", errors.As(err, &errA))
	fmt.Println(errA.Err.Error())
	fmt.Println("Is error B?", errors.As(err, &errB))
	fmt.Println(errB.Err.Error()) // This will cause a panic because `err` is not of type `ErrorB` so `errB` is nil
}

func bar() error {
	err := foo()
	if err != nil {
		return &ErrorA{
			Err:     fmt.Errorf("error in bar: %w", err),
			Message: err.Error(),
		}
	}

	return nil
}

func foo() error {
	return ErrFoo
}

// Output:
// Is error A? true
// error in bar: foo
// Is error B? false
// panic: runtime error: invalid memory address or nil pointer dereference



If you're using dark mode, do you like the code blocks's theme? I have it available for VS Code, feel free to check it.