Skip to content
Theory · Errors · lesson 8 of 11

Error types and errors.As

In the previous lesson the marker ErrNotFound said one thing: "not found". But the calling code often needs details: which user was not found, which code the server answered with. A marker is a "not found" stamp on the delivery note. Details go into a form with fields: "order number", "reason".

In Go such a form is your own error type. It is an ordinary struct with fields and an Error() string method, and so it implements the error interface:

GO
package main

import "fmt"

type NotFoundError struct{ ID int }

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("user %d not found", e.ID)
}

func main() {
    var err error = &NotFoundError{ID: 7}
    fmt.Println(err) // user 7 not found
}

Let's go through the example line by line:

  1. type NotFoundError struct{ ID int } declares a form with one field: the user's number.
  2. The Error method with a pointer receiver builds the error text from the ID field.
  3. var err error = &NotFoundError{ID: 7} puts the struct's address into a variable of type error. It has to be the address: the Error method belongs to *NotFoundError, and the struct itself does not have it.
  4. fmt.Println(err) prints the text from the Error method: user 7 not found.

By convention, the name of such a type ends in Error, while a marker's name starts with Err.

The errors.As function

While the error sits in the variable as is, a type assertion can get the number out. But functions wrap errors with %w, and then the outside is an envelope, not the form. The errors.As function finds the form in the chain:

GO
err := fmt.Errorf("load: %w", &NotFoundError{ID: 7})
var nf *NotFoundError
if errors.As(err, &nf) {
    fmt.Println("not found:", nf.ID) // not found: 7
}

Let's go through the example line by line:

  1. err is an envelope marked load: with the NotFoundError form inside.
  2. var nf *NotFoundError declares an empty variable of the type we are looking for.
  3. errors.As(err, &nf) opens the envelopes one by one. When it finds an error of type *NotFoundError, it puts it into nf and returns true.
  4. Inside the if, nf already has its fields, and the program prints not found: 7.

The second argument is the variable's address, &nf: that is how the function can put what it found into it.

errors.Is or errors.As

Interviewers like to ask this: how do the two functions differ? Let's take each one.

errors.Is looks for a value

errors.Is(err, target) looks for that very value target in the chain. This suits a marker: there is one ErrNotFound in the program. Your own type usually is not found by errors.Is:

GO
err := fmt.Errorf("load: %w", &NotFoundError{ID: 7})
fmt.Println(errors.Is(err, &NotFoundError{ID: 7})) // false

Each &NotFoundError{...} is the address of a new struct. They are two houses with the same furniture at different addresses, and == on pointers gives false.

errors.As looks for a type

errors.As(err, &target) looks for any error of the needed type in the chain, whatever values its fields hold. That is why you choose it for your own error types.

Here the form picture stops working: in real life you recognize a form by its heading, while errors.As recognizes an error only by its type. Two different types with the same fields are different forms to it.

Common mistake

The variable itself was passed to errors.As, not its address:

GO
err := fmt.Errorf("load: %w", &NotFoundError{ID: 7})
var nf *NotFoundError
fmt.Println(errors.As(err, nf)) // ✗
TEXT
panic: errors: target must be a non-nil pointer

The program compiles, but it stops with a panic at run time: nf is an empty pointer, and there is nowhere to put what was found. The go vet command catches this mistake in advance. Pass the address: errors.As(err, &nf).

Key points

  • Your own error type is a struct with fields and an Error() string method; it carries details a marker does not have.
  • errors.As(err, &target) looks for an error of the needed type in the chain and puts it into target.
  • errors.Is looks for a particular value and suits markers, while errors.As looks for a type and suits your own error types.
  • The second argument of errors.As is a variable's address: &nf, not nf.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way