82

I am new to golang, my application needs to return multiple errors in a loop, later requires to be combined and returned as a single error string. I am not able to use the string functions to combine the error messages. What methods can be use to combine these errors into a single error before returning?

package main

import (
   "fmt"
   "strings"
)

func Servreturn() (err error) {

   err1 = fmt.Errorf("Something else occurred")
   err2 = fmt.Errorf("Something else occurred again")

   // concatenate both errors

   return err3

}
4
  • Not enought information. What the client do? What your server do? A sample output. Commented Nov 3, 2015 at 6:34
  • A future version (Go 1.2x, for 2023) should provide a way to return a slice/tree of errors. Commented Sep 17, 2022 at 21:12
  • downvote: concatenating the string messages is not that interesting, I'm looking for way to preserve the error objects and this question appears not to be about stacking or joining errors. Commented Jan 11, 2023 at 8:49
  • Go 1.20 adds multi-error wrapping. See this answer. Commented Feb 3, 2023 at 9:25

11 Answers 11

131

UPDATE for Go 1.20:

As of Go version 1.20 you can join errors with the new errors.Join function (nil errors are ignored):

err = errors.Join(err, nil, err2, err3)

Playground Example for errors.Join

Also, the fmt.Errorf function now supports wrapping multiple errors with multiple %w format verbs:

err := fmt.Errorf("%w; %w; %w", err, err2, err3)

Playground Example for fmt.Errorf


UPDATE for Go 1.13:

As of Go version 1.13, the language's errors package now supports error wrapping directly.

You can wrap an error by using the %w verb in fmt.Errorf:

err := errors.New("Original error")
err = fmt.Errorf("%w; Second error", err)

Use Unwrap to remove the last error added, and return what remains: previousErrors := errors.Unwrap(err)

Playground Example for errors.Unwrap

Two more functions, errors.Is and errors.As provide ways to check for and retrieve a specific type of error.

Playground Example for errors.As and errors.Is


Dave Cheney's excellent errors package (https://github.com/pkg/errors) include a Wrap function for this purpose:

package main

import "fmt"
import "github.com/pkg/errors"

func main() {
        err := errors.New("error")
        err = errors.Wrap(err, "open failed")
        err = errors.Wrap(err, "read config failed")

        fmt.Println(err) // "read config failed: open failed: error"
}

This also allows additional functionality, such as unpacking the cause of the error:

package main

import "fmt"
import "github.com/pkg/errors"

func main() {
    err := errors.New("original error")
    err = errors.Wrap(err, "now this")

    fmt.Println(errors.Cause(err)) // "original error"
}

As well as the option to output a stack trace when specifying fmt.Printf("%+v\n", err).

You can find additional information about the package on his blog: here and here.

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

7 Comments

Wrapping is not for a multiple errors. It is for wrapping ONE error with more context.
@dolmen can you provide a citation on that assertion?
@dolmen what is that supposed to mean?
@Paulo: @dolmen is asserting that error wrapping is most intuitive when used like InnerExceptions in other languages. a wrapped error is not a list of (sibling) errors, but a chain of (parent-child) errors. there's nothing that stops you from using it like a list, but IMO it is counterintuitive.
Go 1.20 supports this natively with errors.Join. See this answer for more details: stackoverflow.com/a/74645028/115363
|
31

You could use the strings.Join() and append() function to acheive this slice.

example: golang playgorund

package main

import (
    "fmt"
    "strings"
    "syscall"
)

func main() {

    // create a slice for the errors
    var errstrings []string 

    // first error
    err1 := fmt.Errorf("First error:server error")
    errstrings = append(errstrings, err1.Error())

    // do something 
    err2 := fmt.Errorf("Second error:%s", syscall.ENOPKG.Error())
    errstrings = append(errstrings, err2.Error())

    // do something else
    err3 := fmt.Errorf("Third error:%s", syscall.ENOTCONN.Error())
    errstrings = append(errstrings, err3.Error())

    // combine and print all the error
    fmt.Println(fmt.Errorf(strings.Join(errstrings, "\n")))


}

This would output a single string which you can send back to the client.

First error:server1 
Second error:Package not installed 
Third error:Socket is not connected

hope this helps!

1 Comment

You can now use errors.Join instead: stackoverflow.com/a/74645028/115363
29

String functions don't work on errors because error is really an interface that implements the function Error() string.

You can use string functions on err1.Error() and err2.Error() but not on the "err1" reference itself.

Some errors are structs, like the ones you get from database drivers.

So there's no natural way to use string functions on errors since they may not actually be strings underneath.

As for combining two errors:

Easy, just use fmt.Errorf again.

fmt.Errorf("Combined error: %v %v", err1, err2)

Alternatively:

errors.New(err1.Error() + err2.Error())

4 Comments

Any other ways other than using fmt.Errorf(), why cant I use the string function ?
Sure. Depends on what you really mean by "combining". String concatenation is done using the + operator. Or you might store them individually in a slice, append()-ing values to it. This question suggests you should work yourself through a Go tutorial and get a book on it.
@GregPetr Potentially another clean way to do this is to have your own CombinedError struct that takes a list of errors as a field and implements the Error() string method and do what you want, whether its concatenating strings or whatsoever.
This way is not standard, and while it is strange, it is even more exhausting. Stick to the standard ways, if you want your code to be readable and developable for other developers. Use fmt.Errorf or errors.Join instead.
17

As of Go 1.20, we'll be able to wrap multiple errors using errors.Join.

See this proposal for more details.

First Option

You can print the errors; they will be separated by a newline character when you do that.

var (
    ErrIncorrectUsername = errors.New("incorrect username")
    ErrIncorrectPassword = errors.New("incorrect password")
)

func main() {
    err := validate("ruster", "4321")
    // You can print multi-line errors
    // Each will be separated by a newline character (\n).
    if err != nil {
        fmt.Println(err)
        // incorrect username
        // incorrect password
    }
}

func validate(username, password string) error {
    var errs []error

    // errors.Join the errors into a single error
    if username != "gopher" {
        errs = append(errs, ErrIncorrectUsername)
    }
    if password != "1234" {
        errs = append(errs, ErrIncorrectPassword)
    }

    // Join returns a single `error`.
    // Underlying, the error contains all the errors we add.
    return errors.Join(errs...)
}

Second Option

errors.Join returns an error that contains each error you add. So you can use errors.Is and errors.As to check for individual errors for finer granularity.

// ...
func main() {
    err := validate("ruster", "4321")
    // You can detect each one:
    if errors.Is(err, ErrIncorrectUsername) {
        // handle the error here
    }
    // Or detect the other one:
    if errors.Is(err, ErrIncorrectPassword) {
        // handle the error here
    }
}

func validate(username, password string) error {
    // ...
}

Note: This naive validate example is here to convey the idea. Instead of chaining errors, think errors like a tree. Join allows you to do that when combined with other Joins.


Run both on Go Playground.

Comments

13

To expand on what @WillC had mentioned in a comment it is possible to define your own error type as error is an interface type. Any type that implements a Error() string function implements the error interface. Therefore, you could create a CollectionError which aggregates errors and returns a concatenated error string.

type ErrorCollector []error

func (c *ErrorCollector) Collect(e error) { *c = append(*c, e) }

func (c *ErrorCollector) Error() (err string) {
    err = "Collected errors:\n"
    for i, e := range *c {
        err += fmt.Sprintf("\tError %d: %s\n", i, e.Error())
    }

    return err
}

This provides a collection function that appends a given error to a slice. Upon calling Error() string it iterates over the slice and creates a concatenated error string.

func main() {
    collector := new(ErrorCollector)

    for i := 0; i < 10; i++ {
         collector.Collect(errors.New(fmt.Sprintf("%d Error", i)))
    }

    fmt.Println(collector)
}

There is a great golang.org blog post going over errors in more detail. A full example of the example is available on The Go Playground.

5 Comments

thanks, your example was very useful. I will try this out and get back.
@BenCampbell Is there a reason for the method receiver to be a pointer here?
Thanks for this tip. On the off chance one might add a nil error, the Error() function should do a nil check on the collected errors. And if one accesses by just index and not index + value, need to deference cast the error array before the index access. Here's an example: play.golang.org/p/h9F5WhcQ5Bs
@Rakesh No. func (c *ErrorCollector) Error() (err string) should be func (c ErrorCollector) Error() (err string).
You don’t need to, but it would violate convention to have some methods be pointer receivers and some to be value receivers. Collect needs to be a pointer receiver.
12

Uber has a multierr package for this use case:

return multierr.Combine(err1, err2)

1 Comment

As of Go 1.20, it is natively supported: stackoverflow.com/a/74645028/115363.
9

People may be interested in https://github.com/hashicorp/go-multierror which describes itself as "A Go (golang) package for representing a list of errors as a single error.".

1 Comment

It comes with MPL license which requires you to disclose the source code
3

This seems to work well for me (space separated errors):

  1. Put all your errors in a error slice/list/array, ie: var errors [] error
  2. fmt.Sprintf("%s", errors)
    var errors []error
    errors = append(errors, fmt.Errorf("error 1"))
    errors = append(errors, fmt.Errorf("error 2"))
    errors = append(errors, fmt.Errorf("and yet another error"))
    s := fmt.Sprintf("%s", errors)
    fmt.Println(s)

Comments

1

Sometimes i need the way to detect if there some error in the chain. the standard way, provided with https://pkg.go.dev/errors is pretty convenient:

someErr:=errors.New("my error")
fmt.Errorf("can't process request: %w",someErr) 
...
err:=f()
if errors.Is(err,someErr){...}

But it could be applied only in the case of detection of the last error in the chain. You can't wrap someErr1 and then someErr2 and then get true from both of checks: errors.Is(err,someErr1) and errors.Is(err,someErr2)

I solved this problem with following type:

func NewJoinedErrors(err1 error, err2 error) JoinedErrors {
    return JoinedErrors{err1: err1, err2: err2}
}

type JoinedErrors struct {
    err1 error
    err2 error
}

func (e JoinedErrors) Error() string {
    return fmt.Sprintf("%s: %s", e.err1, e.err2)
}

func (e JoinedErrors) Unwrap() error {
    return e.err2
}

func (e JoinedErrors) Is(target error) bool {
    return errors.Is(e.err1, target)
}

It uses the fact, that

An error is considered to match a target if it is equal to that target or if it implements a method Is(error) bool such that Is(target) returns true.

So you can join two errors and get positive result on both checks:

someErr1:=errors.New("my error 1")
someErr2:=errors.New("my error 2")
err:=NewJoinedErrors(someErr1, someErr2)
// this will be true because 
// (e JoinedErrors) Is(target error) 
// will return true 
if errors.Is(err, someErr1){...}
// this will be true because 
// (e JoinedErrors) Unwrap() error 
// will return err2 
if errors.Is(err, someErr2){...}

you can check it out here: https://play.golang.org/p/W7NGyfvr0v_N

Comments

0
func condenseErrors(errs []error) error {
    switch len(errs) {
    case 0:
        return nil
    case 1:
        return errs[0]
    }
    err := errs[0]
    for _, e := range errs[1:] {
        err = errors.Wrap(err, e.Error())
    }
    return err
}

2 Comments

pay attention that if first error is nil e.g [nil, error1, error2, error3] you code will return nil because of the Wrap implementation ``` func Wrap(err error, message string) error { if err == nil { return nil } err = &withMessage{ cause: err, msg: message, } return &withStack{ err, callers(), } } ```
Good point. This assumes that errs passed to the function are not nil.
-1

Use this function:

func JoinErrs(errs ...error) error {
    var joinErrsR func(string, int, ...error) error
    joinErrsR = func(soFar string, count int, errs ...error) error {
        if len(errs) == 0 {
            if count == 0 {
                return nil
            }
            return fmt.Errorf(soFar)
        }
        current := errs[0]
        next := errs[1:]
        if current == nil {
            return joinErrsR(soFar, count, next...)
        }
        count++
        if count == 1 {
            return joinErrsR(fmt.Sprintf("%s", current), count, next...)
        } else if count == 2 {
            return joinErrsR(fmt.Sprintf("1: %s\n2: %s", soFar, current), count, next...)
        }
        return joinErrsR(fmt.Sprintf("%s\n%d: %s", soFar, count, current), count, next...)
    }
    return joinErrsR("", 0, errs...)
}

It will give you nil when all errors are nil, will give you the same error when just one non-nil error, will give you numbered list of non-nil errors when multiple non-nil errors

Try it out here: https://play.golang.org/p/WttztCr-xHG

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.