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:
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:
type NotFoundError struct{ ID int }declares a form with one field: the user's number.- The
Errormethod with a pointer receiver builds the error text from theIDfield. var err error = &NotFoundError{ID: 7}puts the struct's address into a variable of typeerror. It has to be the address: theErrormethod belongs to*NotFoundError, and the struct itself does not have it.fmt.Println(err)prints the text from theErrormethod: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:
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:
erris an envelope markedload:with theNotFoundErrorform inside.var nf *NotFoundErrordeclares an empty variable of the type we are looking for.errors.As(err, &nf)opens the envelopes one by one. When it finds an error of type*NotFoundError, it puts it intonfand returnstrue.- Inside the
if,nfalready has its fields, and the program printsnot 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:
err := fmt.Errorf("load: %w", &NotFoundError{ID: 7})
fmt.Println(errors.Is(err, &NotFoundError{ID: 7})) // falseEach &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:
err := fmt.Errorf("load: %w", &NotFoundError{ID: 7})
var nf *NotFoundError
fmt.Println(errors.As(err, nf)) // ✗panic: errors: target must be a non-nil pointerThe 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() stringmethod; 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 intotarget.errors.Islooks for a particular value and suits markers, whileerrors.Aslooks for a type and suits your own error types.- The second argument of
errors.Asis a variable's address:&nf, notnf.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.