Go - Errors
Error states are represented with error
values.
Error Guidelines
- The
nil
value represents no error (The zero value for the interface type) - A function’s error value should be the last return value
- On error a function should return zero values for its other return parameters
- Error messages should not be capitalized
- Error messages should not end with punctuation or a newline
- Error variables are named starting with
err
orErr
Error Type
The error
type is a built-in interface.
// Any type that defines an `Error()` method implements the `error` interface.
// The `fmt` package formats an error value by calling its `Error()` method.
type error interface {
Error() string
}
Error Handling
Errors are checked by inspecting a function’s error return value, if the error value is not nil
it means an error has occured.
// A function that might return an error.
// A nil value is returned on success.
err := SomeFunction()
// A non-nil error denotes failure.
// Check for errors by testing whether the value is not equal to nil.
if err != nil {
// Handle the error
}
Creating Errors
Create an error
for a given error message:
import "errors"
// `errors.New` returns a pointer to a struct that holds the error message.
err := errors.New("connection error")
// Each call to `errors.New` returns a distinct error value
// even if the text is identical.
errors.New("not found") == errors.New("not found") // false
Create an error
with a formatted error message:
import "fmt"
// `fmt.Errorf` is a wrapper for `errors.New` with string formatting.
err := fmt.Errorf("user %s (ID %d) was not found", name, id)
Equivalent to:
import (
"errors"
"fmt"
)
err := errors.New(fmt.Sprintf("user %s (ID %d) was not found", name, id))
Custom Error Types
Since error
is an interface, any value that has a method Error() string
can be used as an error.
Custom error types can be used to add additional details to the error to be inspected when needed.
// Define a struct to hold the error message with the additional data.
// This error type holds an error message with an integer code.
type CustomError struct {
Msg string
Code int
}
// This type implements the error interface by having an `Error` method.
func (e *CustomError) Error() string {
return fmt.Sprintf("error %d: %s", e.Code, e.Msg)
}
Usage:
// A function returning a custom error.
func doSomething() error {
// Create an error with additional data.
// Return a pointer because Error() is implemented on the pointer receiver.
return &CustomError{
Code: 404,
Msg: "not found",
}
}
func main() {
err := doSomething()
// The error can be handled just like any other error.
if err != nil {
fmt.Println(err)
}
}
To access the error’s fields and methods, a type assertion can be used (Not recommended):
func main() {
err := doSomething()
// Use a type assertion to access the error details.
if err, ok := err.(*CustomError); ok {
fmt.Println(err.Code, err.Msg)
}
}
Using errors.As
is the preferred way of accessing the error’s fields and methods, as it works with wrapped errors:
func main() {
err := doSomething()
// Variable to hold the error value.
var customErr *CustomError
if errors.As(err, &customErr) {
fmt.Println(customErr.Code, customErr.Msg)
}
}
Expected Errors
Expected errors or sentinel errors are useful for providing errors to be checked and handled in a specific way.
package example
import "errors"
// An error to be handled in a specific way.
// By convention the error name starts with `Err`.
var ErrNotFound = errors.New("not found")
Errors defined in this way can be compared directly using a simple equality check (Not recommended):
err := SomeFunction()
// This check will not succeed if the error is wrapped.
if err == example.ErrNotFound {
// Handle the error
}
Using errors.Is
is the preferred way of checking for a specific error:
err := SomeFunction()
// This check will succeed even if the error is wrapped.
if errors.Is(err, example.ErrNotFound) {
// Handle the error
}
Wrapping Errors
A wrapped error is simply an error that contains another error.
Wrapping an error is the process of creating a new error that includes the original error along with additional context.
A series of wrapped errors is called an error chain.
An error wraps another error if its type has an Unwrap()
method that returns an error
or []error
value.
Error wrapping is done using fmt.Errorf
with the %w
verb:
err := SomeFunction()
if err != nil {
// Create a new error that wraps (includes) the original error
// with additional context about the failure.
// To create an error that contains the message from the original error
// without wrapping it, use the %v verb instead of %w.
return fmt.Errorf("failed to execute SomeFunction: %w", err)
}
Unwrapping the error is done using errors.Unwrap
(Usually it’s not used directly, errors.Is
and errors.As
are used instead):
err := SomeFunction()
// `errors.Unwrap` returns nil if the error does not wrap any other error.
if wrappedErr := errors.Unwrap(err); wrappedErr != nil {
fmt.Println(wrappedErr) // Handle the original error
}
When wrapping an error, a new error is created that includes the original error:
// The error type created by the `fmt.Errorf` function.
type wrapError struct {
msg string // Error message
err error // Wrapped (original) error
}
func (e *wrapError) Error() string {
return e.msg
}
// `Unwrap` returns the original wrapped error.
func (e *wrapError) Unwrap() error {
return e.err
}
When wrapping errors with a custom error type it needs to implement an Unwrap()
method:
type CustomError struct {
Msg string
Code int
Err error // Wrapped error
}
func (e *CustomError) Error() string {
return e.Msg
}
// Return the wrapped error with an `Unwrap` method.
// Called by `errors.Unwrap` to retrieve the wrapped error.
func (e *CustomError) Unwrap() error {
return e.Err
}
func doSomething() error {
err := SomeFunction()
// Use `CustomError` to wrap other errors.
if err != nil {
return &CustomError{
Msg: "doSomething failed",
Code: 1000,
Err: err // The original error
}
}
return nil
}
Checking Error Values
errors.Is
is used to check for a specific error value or type.
err := SomeFunction()
if errors.Is(err, ErrSomeError) {
// Handle the error
}
errors.As
is used to perform a type assertion on an error and access its fields and methods.
err := SomeFunction()
// Declare a variable of the error type to check.
var customErr *CustomError
// Pass a pointer to the variable to `errors.As` to be assigned.
if errors.As(err, &customErr) {
// Access the error's fields and methods.
fmt.Println(customErr.Code)
}
A pointer to an interface can be used with errors.As
:
err := SomeFunction()
// Using an anonymous interface.
// Check for errors with a `Code()` method.
var errWithCode interface {
Code() int
}
// Pass the pointer to the interface.
if errors.As(err, &errWithCode) {
fmt.Println(errWithCode.Code()) // Use the interface value
}