2

Say I have the following code:

package lib

import (
    "errors"
    "strconv"
)

var ErrSomething = errors.New("foobar")

func SomeFunc(str string) (int, error) {
    i, err := strconv.Atoi(str)
    if err != nil {
        // how to combine ErrSomething with err?
        return 0, fmt.Errorf("%w: %w", ErrSomething, err)
    }
    
    // do other things, potentially return other errors

    return i

}

How do I combine the error returned from strconv.Atoi with my named error ErrSomething. The reason for combining is so that users of my SomeFunc() can check what exactly went wrong using my error "constants" while not losing information about the underlying error.

I have read similar questions but the usual answer is to just do: return 0, fmt.Errorf("foobar: %w", err) but this way my users can't check the error using errors.Is(err, ???)

4
  • 1
    You might be interested in the following library: github.com/hashicorp/go-multierror Commented Jan 1, 2022 at 18:36
  • It seems that Errorf can only wrap 1 error. Have you tried to turn the underlying error to string: fmt.Errorf("%w: %s", ErrSomething, err) ? Commented Jan 1, 2022 at 19:14
  • 1
    On multiple errors, see also the future (Go 1.2x, for 2023) with a slice/tree of errors which does include a Join() method. Commented Sep 17, 2022 at 21:10
  • 1
    With Go 1.20 there is errors.Join(): tip.golang.org/doc/go1.20#errors Commented Jan 19, 2023 at 9:05

1 Answer 1

4

You can achieve the desired behavior by creating an error type that implements the Is and Unwrap methods as follows:

package lib

import (
    "fmt"
    "strconv"
)

type FoobarError struct {
    msg      string
    original error
}

func (err *FoobarError) Error() string {
    return fmt.Sprintf("%s: %s", err.msg, err.original.Error())
}

func (err *FoobarError) Unwrap() error {
    return err.original
}

func (err *FoobarError) Is(target error) bool {
    _, ok := target.(*FoobarError)
    return ok
}

func SomeFunc() error {
    // strconv.ErrSyntax is used as a dummy error here for the error
    // that might be returned by strconv.Atoi or any other operation.
    err := strconv.ErrSyntax
    return &FoobarError{"foobar", err}
}

Usage:

package main

import (
    "errors"
    "fmt"
    "strconv"

    "lib"
)

func main() {
    if err := lib.SomeFunc(); err != nil {
        fmt.Println(err)                                // foobar: invalid syntax
        fmt.Println(errors.Is(err, &lib.FoobarError{})) // true
        fmt.Println(errors.Is(err, strconv.ErrSyntax))  // true
    }
}

You can read more about this approach here.

Bonus

Similar to Go's os.IsExist, you may be interested in adding a helper function to your library that makes it easier for the user to check the error:

package lib

import (
  "errors"
  // ...
)

// ...

func IsFoobar(err error) bool {
    return errors.Is(err, &FoobarError{})
}

Usage:

package main

// ...

func main() {
    err := lib.SomeFunc(); 
    if lib.IsFoobar(err) {
      // ...
    }
}

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.