Source File
error.go
Belonging Package
go.uber.org/multierr
// Copyright (c) 2017-2023 Uber Technologies, Inc.//// Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to deal// in the Software without restriction, including without limitation the rights// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell// copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions://// The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN// THE SOFTWARE.// Package multierr allows combining one or more errors together.//// # Overview//// Errors can be combined with the use of the Combine function.//// multierr.Combine(// reader.Close(),// writer.Close(),// conn.Close(),// )//// If only two errors are being combined, the Append function may be used// instead.//// err = multierr.Append(reader.Close(), writer.Close())//// The underlying list of errors for a returned error object may be retrieved// with the Errors function.//// errors := multierr.Errors(err)// if len(errors) > 0 {// fmt.Println("The following errors occurred:", errors)// }//// # Appending from a loop//// You sometimes need to append into an error from a loop.//// var err error// for _, item := range items {// err = multierr.Append(err, process(item))// }//// Cases like this may require knowledge of whether an individual instance// failed. This usually requires introduction of a new variable.//// var err error// for _, item := range items {// if perr := process(item); perr != nil {// log.Warn("skipping item", item)// err = multierr.Append(err, perr)// }// }//// multierr includes AppendInto to simplify cases like this.//// var err error// for _, item := range items {// if multierr.AppendInto(&err, process(item)) {// log.Warn("skipping item", item)// }// }//// This will append the error into the err variable, and return true if that// individual error was non-nil.//// See [AppendInto] for more information.//// # Deferred Functions//// Go makes it possible to modify the return value of a function in a defer// block if the function was using named returns. This makes it possible to// record resource cleanup failures from deferred blocks.//// func sendRequest(req Request) (err error) {// conn, err := openConnection()// if err != nil {// return err// }// defer func() {// err = multierr.Append(err, conn.Close())// }()// // ...// }//// multierr provides the Invoker type and AppendInvoke function to make cases// like the above simpler and obviate the need for a closure. The following is// roughly equivalent to the example above.//// func sendRequest(req Request) (err error) {// conn, err := openConnection()// if err != nil {// return err// }// defer multierr.AppendInvoke(&err, multierr.Close(conn))// // ...// }//// See [AppendInvoke] and [Invoker] for more information.//// NOTE: If you're modifying an error from inside a defer, you MUST use a named// return value for that function.//// # Advanced Usage//// Errors returned by Combine and Append MAY implement the following// interface.//// type errorGroup interface {// // Returns a slice containing the underlying list of errors.// //// // This slice MUST NOT be modified by the caller.// Errors() []error// }//// Note that if you need access to list of errors behind a multierr error, you// should prefer using the Errors function. That said, if you need cheap// read-only access to the underlying errors slice, you can attempt to cast// the error to this interface. You MUST handle the failure case gracefully// because errors returned by Combine and Append are not guaranteed to// implement this interface.//// var errors []error// group, ok := err.(errorGroup)// if ok {// errors = group.Errors()// } else {// errors = []error{err}// }package multierr // import "go.uber.org/multierr"import ()var (// Separator for single-line error messages._singlelineSeparator = []byte("; ")// Prefix for multi-line messages_multilinePrefix = []byte("the following errors occurred:")// Prefix for the first and following lines of an item in a list of// multi-line error messages.//// For example, if a single item is://// foo// bar//// It will become,//// - foo// bar_multilineSeparator = []byte("\n - ")_multilineIndent = []byte(" "))// _bufferPool is a pool of bytes.Buffers.var _bufferPool = sync.Pool{New: func() interface{} {return &bytes.Buffer{}},}type errorGroup interface {Errors() []error}// Errors returns a slice containing zero or more errors that the supplied// error is composed of. If the error is nil, a nil slice is returned.//// err := multierr.Append(r.Close(), w.Close())// errors := multierr.Errors(err)//// If the error is not composed of other errors, the returned slice contains// just the error that was passed in.//// Callers of this function are free to modify the returned slice.func ( error) []error {return extractErrors()}// multiError is an error that holds one or more errors.//// An instance of this is guaranteed to be non-empty and flattened. That is,// none of the errors inside multiError are other multiErrors.//// multiError formats to a semi-colon delimited list of error messages with// %v and with a more readable multi-line format with %+v.type multiError struct {copyNeeded atomic.Boolerrors []error}// Errors returns the list of underlying errors.//// This slice MUST NOT be modified.func ( *multiError) () []error {if == nil {return nil}return .errors}func ( *multiError) () string {if == nil {return ""}:= _bufferPool.Get().(*bytes.Buffer).Reset().writeSingleline():= .String()_bufferPool.Put()return}// Every compares every error in the given err against the given target error// using [errors.Is], and returns true only if every comparison returned true.func ( error, error) bool {for , := range extractErrors() {if !errors.Is(, ) {return false}}return true}func ( *multiError) ( fmt.State, rune) {if == 'v' && .Flag('+') {.writeMultiline()} else {.writeSingleline()}}func ( *multiError) ( io.Writer) {:= truefor , := range .errors {if {= false} else {.Write(_singlelineSeparator)}io.WriteString(, .Error())}}func ( *multiError) ( io.Writer) {.Write(_multilinePrefix)for , := range .errors {.Write(_multilineSeparator)writePrefixLine(, _multilineIndent, fmt.Sprintf("%+v", ))}}// Writes s to the writer with the given prefix added before each line after// the first.func ( io.Writer, []byte, string) {:= truefor len() > 0 {if {= false} else {.Write()}:= strings.IndexByte(, '\n')if < 0 {= len() - 1}io.WriteString(, [:+1])= [+1:]}}type inspectResult struct {// Number of top-level non-nil errorsCount int// Total number of errors including multiErrorsCapacity int// Index of the first non-nil error in the list. Value is meaningless if// Count is zero.FirstErrorIdx int// Whether the list contains at least one multiErrorContainsMultiError bool}// Inspects the given slice of errors so that we can efficiently allocate// space for it.func ( []error) ( inspectResult) {:= truefor , := range {if == nil {continue}.Count++if {= false.FirstErrorIdx =}if , := .(*multiError); {.Capacity += len(.errors).ContainsMultiError = true} else {.Capacity++}}return}// fromSlice converts the given list of errors into a single error.func ( []error) error {// Don't pay to inspect small slices.switch len() {case 0:return nilcase 1:return [0]}:= inspect()switch .Count {case 0:return nilcase 1:// only one non-nil entryreturn [.FirstErrorIdx]case len():if !.ContainsMultiError {// Error list is flat. Make a copy of it// Otherwise "errors" escapes to the heap// unconditionally for all other cases.// This lets us optimize for the "no errors" case.:= append(([]error)(nil), ...)return &multiError{errors: }}}:= make([]error, 0, .Capacity)for , := range [.FirstErrorIdx:] {if == nil {continue}if , := .(*multiError); {= append(, .errors...)} else {= append(, )}}return &multiError{errors: }}// Combine combines the passed errors into a single error.//// If zero arguments were passed or if all items are nil, a nil error is// returned.//// Combine(nil, nil) // == nil//// If only a single error was passed, it is returned as-is.//// Combine(err) // == err//// Combine skips over nil arguments so this function may be used to combine// together errors from operations that fail independently of each other.//// multierr.Combine(// reader.Close(),// writer.Close(),// pipe.Close(),// )//// If any of the passed errors is a multierr error, it will be flattened along// with the other errors.//// multierr.Combine(multierr.Combine(err1, err2), err3)// // is the same as// multierr.Combine(err1, err2, err3)//// The returned error formats into a readable multi-line error message if// formatted with %+v.//// fmt.Sprintf("%+v", multierr.Combine(err1, err2))func ( ...error) error {return fromSlice()}// Append appends the given errors together. Either value may be nil.//// This function is a specialization of Combine for the common case where// there are only two errors.//// err = multierr.Append(reader.Close(), writer.Close())//// The following pattern may also be used to record failure of deferred// operations without losing information about the original error.//// func doSomething(..) (err error) {// f := acquireResource()// defer func() {// err = multierr.Append(err, f.Close())// }()//// Note that the variable MUST be a named return to append an error to it from// the defer statement. See also [AppendInvoke].func ( error, error) error {switch {case == nil:returncase == nil:return}if , := .(*multiError); ! {if , := .(*multiError); && !.copyNeeded.Swap(true) {// Common case where the error on the left is constantly being// appended to.:= append(.errors, )return &multiError{errors: }} else if ! {// Both errors are single errors.return &multiError{errors: []error{, }}}}// Either right or both, left and right, are multiErrors. Rely on usual// expensive logic.:= [2]error{, }return fromSlice([0:])}// AppendInto appends an error into the destination of an error pointer and// returns whether the error being appended was non-nil.//// var err error// multierr.AppendInto(&err, r.Close())// multierr.AppendInto(&err, w.Close())//// The above is equivalent to,//// err := multierr.Append(r.Close(), w.Close())//// As AppendInto reports whether the provided error was non-nil, it may be// used to build a multierr error in a loop more ergonomically. For example://// var err error// for line := range lines {// var item Item// if multierr.AppendInto(&err, parse(line, &item)) {// continue// }// items = append(items, item)// }//// Compare this with a version that relies solely on Append://// var err error// for line := range lines {// var item Item// if parseErr := parse(line, &item); parseErr != nil {// err = multierr.Append(err, parseErr)// continue// }// items = append(items, item)// }func ( *error, error) ( bool) {if == nil {// We panic if 'into' is nil. This is not documented above// because suggesting that the pointer must be non-nil may// confuse users into thinking that the error that it points// to must be non-nil.panic("misuse of multierr.AppendInto: into pointer must not be nil")}if == nil {return false}* = Append(*, )return true}// Invoker is an operation that may fail with an error. Use it with// AppendInvoke to append the result of calling the function into an error.// This allows you to conveniently defer capture of failing operations.//// See also, [Close] and [Invoke].type Invoker interface {Invoke() error}// Invoke wraps a function which may fail with an error to match the Invoker// interface. Use it to supply functions matching this signature to// AppendInvoke.//// For example,//// func processReader(r io.Reader) (err error) {// scanner := bufio.NewScanner(r)// defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err))// for scanner.Scan() {// // ...// }// // ...// }//// In this example, the following line will construct the Invoker right away,// but defer the invocation of scanner.Err() until the function returns.//// defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err))//// Note that the error you're appending to from the defer statement MUST be a// named return.type Invoke func() error// Invoke calls the supplied function and returns its result.func ( Invoke) () error { return () }// Close builds an Invoker that closes the provided io.Closer. Use it with// AppendInvoke to close io.Closers and append their results into an error.//// For example,//// func processFile(path string) (err error) {// f, err := os.Open(path)// if err != nil {// return err// }// defer multierr.AppendInvoke(&err, multierr.Close(f))// return processReader(f)// }//// In this example, multierr.Close will construct the Invoker right away, but// defer the invocation of f.Close until the function returns.//// defer multierr.AppendInvoke(&err, multierr.Close(f))//// Note that the error you're appending to from the defer statement MUST be a// named return.func ( io.Closer) Invoker {return Invoke(.Close)}// AppendInvoke appends the result of calling the given Invoker into the// provided error pointer. Use it with named returns to safely defer// invocation of fallible operations until a function returns, and capture the// resulting errors.//// func doSomething(...) (err error) {// // ...// f, err := openFile(..)// if err != nil {// return err// }//// // multierr will call f.Close() when this function returns and// // if the operation fails, its append its error into the// // returned error.// defer multierr.AppendInvoke(&err, multierr.Close(f))//// scanner := bufio.NewScanner(f)// // Similarly, this scheduled scanner.Err to be called and// // inspected when the function returns and append its error// // into the returned error.// defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err))//// // ...// }//// NOTE: If used with a defer, the error variable MUST be a named return.//// Without defer, AppendInvoke behaves exactly like AppendInto.//// err := // ...// multierr.AppendInvoke(&err, mutltierr.Invoke(foo))//// // ...is roughly equivalent to...//// err := // ...// multierr.AppendInto(&err, foo())//// The advantage of the indirection introduced by Invoker is to make it easy// to defer the invocation of a function. Without this indirection, the// invoked function will be evaluated at the time of the defer block rather// than when the function returns.//// // BAD: This is likely not what the caller intended. This will evaluate// // foo() right away and append its result into the error when the// // function returns.// defer multierr.AppendInto(&err, foo())//// // GOOD: This will defer invocation of foo unutil the function returns.// defer multierr.AppendInvoke(&err, multierr.Invoke(foo))//// multierr provides a few Invoker implementations out of the box for// convenience. See [Invoker] for more information.func ( *error, Invoker) {AppendInto(, .Invoke())}// AppendFunc is a shorthand for [AppendInvoke].// It allows using function or method value directly// without having to wrap it into an [Invoker] interface.//// func doSomething(...) (err error) {// w, err := startWorker(...)// if err != nil {// return err// }//// // multierr will call w.Stop() when this function returns and// // if the operation fails, it appends its error into the// // returned error.// defer multierr.AppendFunc(&err, w.Stop)// }func ( *error, func() error) {AppendInvoke(, Invoke())}
The pages are generated with Golds v0.7.6. (GOOS=linux GOARCH=amd64)