Error handling basics
Error Types
We prefer the use of the CockroachDB errors library at github.com/cockroachdb/errors. This is a superset of Go's own errors and pkg/errors.
errors.New()
for errors with simple static stringserrors.Newf()
for formatted error stringserrors.AssertionFailedf()
for assertion / internal errors that reflect a likely implementation bug by the CRL team and should be louder than usualAdd context on return paths using
errors.Wrap()
/errors.Wrapf()
Add user-directed hints with errors.WithHint()
See the doc README on the errors package for more
When returning errors, consider the following to determine the best choice:
Is this a simple error that needs no extra information? If so,
errors.New
should suffice.Do the clients need to detect and handle this error? If so, you should use a custom type, and implement the
Error()
method.Are you propagating an error returned by a downstream function? If so, check the RFC on error handling
If the client needs to detect the error, and you have created a simple error using errors.New
, use a var for the error.
Bad | Good |
---|---|
// package foo
func Open() error {
return errors.New("could not open")
}
// package bar
if err := foo.Open(); err != nil {
if err.Error() == "could not open" {
// handle
} else {
panic("unknown error")
}
}
| // package foo
var ErrCouldNotOpen = errors.New("could not open")
func Open() error {
return ErrCouldNotOpen
}
// package bar
if err := foo.Open(); err != nil {
if errors.Is(err, foo.ErrCouldNotOpen) {
// handle
} else {
return errors.Wrap(err, "unknown error")
}
} |
If you have an error that clients may need to detect, and you would like to add more information to it (e.g., it is not a static string), then you should use a custom type.
Bad | Good |
---|---|
func open(file string) error {
return fmt.Errorf("file %q not found", file)
}
func use() error {
if err := open(); err != nil {
if strings.Contains(err.Error(), "not found") {
// handle
} else {
panic("unknown error")
}
}
} |
Error Wrapping
There are three main options for propagating errors if a call fails:
Return the original error if there is no additional context to add and you want to maintain the original error type.
Add context using
errors.Wrap
so that the error message provides more context anderrors.Unwrap
can be used to extract the original error. Prefer this tofmt.Errorf("some message %w")
.If you pass an error through a channel from one goroutine to another, use
errors.WithStack
on both ends to identify this flow.Use
errors.Newf
if the callers do not need to detect or handle that specific error case.Use
errors.Handle
orerrors.NewAssertionErrorWithWrappedErrf
to hide the original cause.
It is recommended to add context where possible so that instead of a vague error such as "connection refused", you get more useful errors such as "call service foo: connection refused".
When adding context to returned errors, keep the context succinct by avoiding phrases like "failed to", which state the obvious and pile up as the error percolates up through the stack:
Bad | Good |
---|---|
However once the error is sent to another system, it should be clear the message is an error (e.g. an err
tag or "Failed" prefix in logs).
See also Don't just check errors, handle them gracefully — but be mindful that Go has evolved since this article was written, and use errors.Is()
and errors.As()
where applicable.
Handle Type Assertion Failures
The single return value form of a type assertion will panic on an incorrect type. Therefore, always use the "comma ok" idiom.
Bad | Good |
---|---|
Don't Panic
Code running in production must avoid panics. Panics are a major source of cascading failures. If an error occurs, the function must return an error and allow the caller to decide how to handle it.
Bad | Good |
---|---|
Panic/recover is, in most cases, not an error handling strategy. A program must panic only when something irrecoverable happens such as a nil dereference.
An exception to this is program initialization: bad things at program startup that should abort the program may cause panic.
Another exception is when a package is purely functional and has no side effects. For example, parts of SQL planning in CockroachDB uses panic propagation for error handling in that case.
Even in tests, prefer t.Fatal
or t.FailNow
over panics to ensure that the test is marked as failed.
Bad | Good |
---|---|
Copyright (C) Cockroach Labs.
Attention: This documentation is provided on an "as is" basis, without warranties or conditions of any kind, either express or implied, including, without limitation, any warranties or conditions of title, non-infringement, merchantability, or fitness for a particular purpose.