Explain errors.Is and errors.As — when do you use each?
Quick Answer
errors.Is(err, target) checks whether err, or anything it wraps, equals a specific sentinel error value — use it when you want to know "is this that specific known error?" like checking for sql.ErrNoRows or io.EOF. errors.As(err, &target) checks whether err, or anything it wraps, matches a specific error type, and if so, assigns the matched error into target so you can access its fields — use it when you need to extract structured data from a custom error type, not just confirm identity. Both walk the entire wrap chain automatically via each error's Unwrap() method, which is exactly what makes them work correctly even through several layers of fmt.Errorf("...: %w", err) wrapping.
Detailed Answer
These two functions solve related but distinct problems: "is it this exact error" versus "is it this kind of error, and what data does it carry."
errors.Is: checking identity through a wrap chain
var ErrNotFound = errors.New("not found")
func findUser(id int) error {
return fmt.Errorf("finding user %d: %w", id, ErrNotFound)
}
err := findUser(42)
if errors.Is(err, ErrNotFound) {
fmt.Println("no such user") // true, even though err's message is different text
}
errors.Is walks the chain (err → unwrap → unwrap → ...) checking each layer against ErrNotFound, so wrapping doesn't break the check.
errors.As: extracting a specific error type's data
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}
func validate(input string) error {
if input == "" {
return fmt.Errorf("checking input: %w", &ValidationError{Field: "name", Msg: "cannot be empty"})
}
return nil
}
err := validate("")
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Println("field:", valErr.Field) // "name" — pulled out the concrete type's data
}
errors.As searches the chain for the first error matching *ValidationError, and assigns it into valErr so you can read its specific fields, something errors.Is can't do since it only checks equality.
Choosing between them
| Question you're asking | Use |
|---|---|
"Is this the specific io.EOF / sql.ErrNoRows / my sentinel error?" | errors.Is |
"Is this a *ValidationError (or similar custom type), and what data does it hold?" | errors.As |
A good rule: reach for errors.Is with sentinel error values, and errors.As with custom error types that carry extra data you need to read.