What is the most idiomatic way to handle multiple errors in go?
Should I try to wrap the error and return both?
if err := foo(bar, obj); err != nil {
// how to do I avoid losing this error?
err := l.fixup(obj)
if err != nil {
// but this error is the not the one from foo?
}
return err
}
return l.fixup(obj)
You can add a context to your original error using Wrap
function from this great package from Dave Cheney https://github.com/pkg/errors
errors.Wrap
function returns a new error that adds context to the original error.
func Wrap(cause error, message string) error
in your case this would be:
if cause := foo(bar, obj); cause != nil {
err := l.fixup(obj)
if err != nil {
return errors.Wrap(cause, err.Error())
}
return cause
}
return l.fixup(obj)
The fixup
method is called in both code paths in the question. Simplify the code by calling fixup
outside the if statement.
If you want the error from foo
to take precedence over the error from fixup
, then do just that.
err1 := foo(bar, obj)
err2 := l.fixup(obj)
if err1 != nil {
return err1
}
return err2
If you must chain errors and return, it all depends what your error means and which one you want to notify the caller of. Usually, when the occurence of an error should not stop the path and a call follows, such as foo
here then fixup
, you'd log the first error and return the second one, as it is probably the most relevant to what your function does.
There are also packages to wrap errors, so that you can build an error from multiple errors.
There is the standard package with fmt.Errorf
you could assemble multiple errors.
There is also https://github.com/hashicorp/go-multierror which allows you to keep multiple errors in an error.
In your case, if you want to get both error messages to bubble up, I'd do something like that:
err := foo(bar, obj)
if fixupErr := l.fixup(obj); fixupErr != nil {
if err != nil {
return fmt.Errorf("foo err: %s
fixup err: %s
", err, fixupErr)
}
return fixupErr
}
return err
You code calls l.fixup(obj)
no matter what. If foo(bar, obj)
returns an error, some handling is done and l.fixup(obj)
is called - otherwise only l.fixup(obj)
is called. Hence, your code can be rearranged like this:
// err will only be valid within the if-then-else-construct
if err := foo(bar, obj); err != nil {
// handle error from foo(bar,obj)
// you can even return it, if you wanted to
// For the sake of this example, we simply log it
log.Println("Executing foo: %s", err)
}
return l.fixup(obj)
Furthermore, you can use the fact that error
is an interface to your advantage if you want to distinguish between the error potentially returned by foo
or l.fixup
. You can do that by creating a typed error for one (or both of them) and evaluate the type of the error by using what is called a type switch.
package main
import (
"errors"
"fmt"
)
// FooError is the error to be returned by foo
type FooError struct {
Bar string
}
// Error implements the interface
func (f FooError) Error() string {
return fmt.Sprintf("%s: interface is nil", f.Bar)
}
// dummy foo func
func foo(bar string, in interface{}) error {
if in == nil {
return FooError{Bar: bar}
}
return nil
}
// dummy fixup func
func fixup(in interface{}) error {
if in == nil {
return errors.New("Interface is nil")
}
return nil
}
// a wrapper, containing a variation of above code
func wrap(bar string) error {
if err := foo(bar, nil); err != nil {
// handle error from foo(bar,obj)
// you can even return it, if you wanted to
return err
}
return fixup(nil)
}
func main() {
err := wrap("test")
// The type switch itself
switch err.(type) {
case FooError:
// We have a FooError, so we can deal with it accordingly
fmt.Println("Foo Error:",err)
default:
// Every other error is handled by the default block
fmt.Println("Std Error:",err)
}
}
However, this does not feel quite right. If foo
is called and it returning an error prevents something else in your logic not being executed, it might be valid to panic instead.