A migration guide from github.com/hashicorp/go-multierror to the std library errors package.
In go1.20
support for "multierrors" was added.
Why migrate? #
Migrating from community go modules to standard library implementations is almost always a no-brainer if it provides like for like functionality.
Packages in the core go standard library such as errors
are covered by the go1.0 backwards compatibility guarantee [0].
Because of this they are also guaranteed to be supported with security fixes and other patches.
APIs #
go-multierror #
go-multierror
follows a similar API to how slices are appended to in go.
Playground: https://go.dev/play/p/DrfOauIC1Ou
var result error
if err := step1(); err != nil {
result = multierror.Append(result, err)
}
if err := step2(); err != nil {
result = multierror.Append(result, err)
}
if result != nil {
fmt.Printf("some error: %v", result)
}
multierror.Append
returns a *multierror.Error
which can be further appended to. And if a multierror is appended to another, they will be flattened to make a flat list of errors.
errors.Join #
The standard library implementation is as a new function in the errors
package.
func Join(errs ...error) error
And a new unexported interface.
interface { Unwrap() []error }
Playground: https://go.dev/play/p/D7gFVsBwIbg
err1 := step1()
err2 := step2()
err := errors.Join(err1, err2)
if err != nil {
return fmt.Error("some error: %w", err)
}
Differences #
Although at a high level the APIs are similar, there is one big difference which is that errors.Join
does not flatten other "joined" errors.
Playground: https://go.dev/play/p/f0AGNCkHmC3
var result error
if err := step1(); err != nil {
result = multierror.Append(result, err)
}
if err := step2(); err != nil {
result = multierror.Append(result, err)
}
fmt.Printf("%#v", result)
// [err, err]
Playground: https://go.dev/play/p/k_90D8Yu8oY
var result error
if err := step1(); err != nil {
result = errors.Join(result, err)
}
if err := step2(); err != nil {
result = errors.Join(result, err)
}
result
// [[err], err]
Migrating #
Due to the above differences, when moving from go-multierror
to errors
you should really be calling errors.Join
once unless you want a "tree" of errors.
Set number of errors #
If your code currently looks similar to this:
Playground: https://go.dev/play/p/CtqsNTYl6Jk
var result error
if err := step1(); err != nil {
result = multierror.Append(result, err)
}
if err := step2(); err != nil {
result = multierror.Append(result, err)
}
if result != nil {
// handle error
}
Then you should move to this:
Playground: https://go.dev/play/p/rRXX2SYbuBH
err1 := step1()
err2 := step2()
err := errors.Join(err1, err2)
if err != nil {
// handle error
}
or
Playground: https://go.dev/play/p/rI58PdSVHWj
err := errors.Join(
step1(),
step2(),
)
if err != nil {
// handle error
}
Loops #
If your code currently looks similar to this:
Playground: https://go.dev/play/p/HgEYsuiGivP
var result error
for _, s := range mySlice {
if err := process(s); err != nil {
result = multierror.Append(result, err)
}
}
if result != nil {
// handle error
}
Then you should move to this:
Playground: https://go.dev/play/p/uV7GUzauY31
var errs []error
for _, s := range mySlice {
if err := process(s); err != nil {
errs = append(errs, err)
}
}
err := errors.Join(errs...) // Join the errors once!
if err != nil {
// handle error
}