errors.Is and errors.As
if errors.Is(err, target) {
// matches target
}
var target *CustomError
if errors.As(err, &target) {
// use target
}
errors.Is for Sentinel Errors
errors.Is checks whether a target sentinel error appears anywhere in an error's wrap chain, correctly handling errors that have been wrapped multiple times, unlike a plain == comparison.
Example: errors.Is for Sentinel Errors
// Every Go file belongs to a package; main builds an executable
package main
// Import the packages this program uses
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
// Define the function find
func find(id int) error {
// Check whether id != 1
if id != 1 {
return fmt.Errorf("lookup id %d: %w", id, ErrNotFound)
}
// Send nil back to the caller
return nil
}
// main is where the program starts running
func main() {
// Short declaration: create err and infer the type
err := find(2)
// Check whether errors.Is(err, ErrNotFound)
if errors.Is(err, ErrNotFound) {
fmt.Println("item was not found")
}
}
Login to try C/C++/Java/PHP code in the editor
errors.As for Custom Types
errors.As walks the wrap chain looking for an error that matches a specific concrete type, and if found, assigns it into the target pointer so you can access its fields.
Example: errors.As for Custom Types
// Every Go file belongs to a package; main builds an executable
package main
// Import the packages this program uses
import (
"errors"
"fmt"
)
// Define a struct type named RangeError
type RangeError struct {
Value int
}
// Define the method Error on type RangeError
func (e *RangeError) Error() string {
// Sprintf builds a formatted string instead of printing it
return fmt.Sprintf("value %d out of range", e.Value)
}
// Define the function validate
func validate(n int) error {
// Check whether n > 100
if n > 100 {
return fmt.Errorf("validate: %w", &RangeError{Value: n})
}
// Send nil back to the caller
return nil
}
func main() {
err := validate(150)
var rangeErr *RangeError
if errors.As(err, &rangeErr) {
fmt.Println("bad value was:", rangeErr.Value)
}
}
Login to try C/C++/Java/PHP code in the editor
Why Not Use ==?
Once an error has been wrapped by fmt.Errorf's %w, the resulting error is a new value -- a plain == comparison against the original sentinel would fail even though logically it's 'the same' underlying error, which is exactly the problem errors.Is solves.
Example: Why Not Use ==?
// Every Go file belongs to a package; main builds an executable
package main
// Import the packages this program uses
import (
"errors"
"fmt"
)
var ErrDenied = errors.New("access denied")
// main is where the program starts running
func main() {
// Short declaration: create wrapped and infer the type
wrapped := fmt.Errorf("request failed: %w", ErrDenied)
// Print the values followed by a newline
fmt.Println("== comparison:", wrapped == ErrDenied)
// Print the values followed by a newline
fmt.Println("errors.Is:", errors.Is(wrapped, ErrDenied))
}
Login to try C/C++/Java/PHP code in the editor
- Using == to compare a wrapped error against a sentinel error, which fails once fmt.Errorf %w has wrapped it.
- Confusing errors.Is (checks for a specific sentinel error value) with errors.As (extracts a specific error type).
- Forgetting that both functions walk the whole chain of wrapped errors, not just the outermost one.
- errors.Is checks whether an error chain contains a specific sentinel error value.
- errors.As checks whether an error chain contains a value of a specific error type and extracts it.
- Both functions automatically walk through any wrapped errors created with fmt.Errorf's %w verb.
- Prefer errors.Is/errors.As over direct == comparisons whenever errors might be wrapped.
Chapter Quiz — Complete all 6 topics to unlock
0/6 topics done
Complete these topics first: