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.