4

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)
4
  • Your first line is an example of what you can do, put in in a new block scope. Is there a reason you're not doing that? Commented Mar 22, 2019 at 18:16
  • There is a dependency on foo. If foo fails then fixup needs to see that. Fixup has to be called regardless after foo but has different behaviors based on the result of foo. Really, I want to see the err from foo since that indicates a problem, fixup isn't expected to fail Commented Mar 22, 2019 at 18:24
  • 1
    Then why not name them differently? The obvious solution is usually acceptable. Commented Mar 22, 2019 at 18:30
  • If you need to return multiple errors, see also the future (Go 1.2x, for 2023) with a slice/tree of errors. Commented Sep 17, 2022 at 21:11

4 Answers 4

4

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)
Sign up to request clarification or add additional context in comments.

1 Comment

If the errors are realted, wraping makes sense, say i have a function that does a bunch of validations and then performs an operation, Should I return a (list of errors , result ) or wrap and return (error, result) ?
1

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\nfixup err: %s\n", err, fixupErr)
    }
    return fixupErr
}
return err

Comments

0

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.

Comments

0

After Go 1.20, per the Wrapping multiple errors of release note

The new function errors.Join returns an error wrapping a list of errors

The original proposal is errors: add support for wrapping multiple errors


Sample codes

    if cause := foo(bar, obj); cause != nil {
        err := fixup(obj)
        if err != nil {
            return errors.Join(cause, err)
        }
        return cause
    }
    return fixup(obj)

And you can use errors.Is or errors.As to check for individual errors

var (
    ErrFixup = errors.New("err fixup")
    ErrFoo   = errors.New("err foo")
)

func foo(bar, obj string) error {
    if bar != "" && obj != "" {
        return ErrFoo
    }
    return nil
}

func fixup(obj string) error {
    if obj != "" {
        return ErrFixup
    }
    return nil
}

func test() error {
    bar := "bar"
    obj := "obj"
    if cause := foo(bar, obj); cause != nil {
        err := fixup(obj)
        if err != nil {
            return errors.Join(cause, err)
        }
        return cause
    }
    return fixup(obj)
}

func main() {
    err := test()
    if errors.Is(err, ErrFixup) {
        fmt.Println("err is fixup")
    }
    if errors.Is(err, ErrFoo) {
        fmt.Println("err is foo")
    }
}

Playground

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.