Skip to content

jonbodner/multierr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

multierr

A simple multiple error holder for Go

Motivation

Sometimes you need to return more than one error from a function. The multierr package provides a simple slice of error instances called Error, which meets the error interface.

The way to use this package is via the package-level function Append.

Usage

package main

import (
	"errors"
	"fmt"

	"github.com/jonbodner/multierr"
)

func CallThing(i int) error {
	if i%2 == 1 {
		return errors.New("Error")
	}
	return nil
}

func DoSomethingThatMakesErrors(numCalls int) error {
	var out error
	for i := 0; i < numCalls; i++ {
		e := CallThing(i)
		out = multierr.Append(out, e)
	}
	return out
}

func main() {
	for i := 1; i < 5; i++ {
		fmt.Println("For i == ", i)
		e := DoSomethingThatMakesErrors(i)
		if e != nil {
			switch e := e.(type) {
			case multierr.Error:
				// you can use slice-supporting built-in functions,
				// like len or range
				fmt.Println(len(e), "errors found")
			default:
				fmt.Println("This is a normal error")
			}
			fmt.Println(e.Error())
		} else {
			fmt.Println("No errors!")
		}
	}
}

The Append function will do the right thing if e is nil (it will return its first parameter), so you don't need to wrap calls to it in if e != nil if you are just aggregating errors.

It is very important to notice that out in DoSomethingThatMakesErrors is declared to be of type error, not multierr.Error. This is due to the way that Go handles interface types and nil. For a variable of a non-interface type, having a nil value makes the variable equal to nil. For a variable of an interface type, both the type must be undefined and the value must be nil in order to make the variable equal to nil.

Look at this example:

package main

import (
	"fmt"

	"github.com/jonbodner/multierr"
)

func meErr() error {
	var out multierr.Error
	fmt.Println(out == nil)
	return out
}

func main() {
	e := meErr()
	if e != nil {
		fmt.Println("There was an error")
	}
}

If you run this code, it will print out:

true
There was an error

If the type of out is multierr.Error and no errors are ever appended to it, out is equal to nil in the meErr function. But once the value is returned, it is assigned to a variable of type error, which is an interface. The returned value has a type of multierr.Error and a value of nil. This is sufficient for it to be considered non-nil, which makes e != nil true.

The multierr.Append function uses reflection to properly handle parameters where the underlying value is nil. This will work correctly:

package main

import (
	"github.com/jonbodner/multierr"
	"fmt"
	"errors"
)

func main() {
	var e error
	var e2 multierr.Error

	e = multierr.Append(e, e2)
	checkError(e)

	e = multierr.Append(e, errors.New("This is an error"))
	checkError(e)

	e = multierr.Append(e, errors.New("I'm a second error"))
	checkError(e)

	e2 = multierr.Error{errors.New("I'm a third error"), errors.New("I'm a fourth error")}
	e = multierr.Append(e, e2)
	checkError(e)
}

func checkError(e error) {
	fmt.Println("is nil",e == nil)
	switch e := e.(type) {
	case multierr.Error:
		fmt.Println("This is a multierr of length",len(e))
	default:
		fmt.Println("This is a single error")
	}
}

If you run this code, it will print out:

is nil true
This is a single error
is nil false
This is a single error
is nil false
This is a multierr of length 2
is nil false
This is a multierr of length 4

Is and As

multierr.Error supports the errors.Is and errors.As functions that were added in Go 1.13. The functions return true if any of the errors contained in the multierror match the target for Is or As. errors.Is will also return true when you compare two multierrors with identical contents.

Wrapping vs. Multierror

When would you want to wrap an error vs. using a multierror? A multierror is useful when there are multiple simultaneous problems (such as multiple validation failures for a single struct). Wrapping errors is useful when there is a single error, but you want to add additional information to describe the error or the place in the code where it happened.

About

A simple multiple error holder for Go

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages