uber-go/multierrで複数のerrorをまとめる

# multierr
https://github.com/uber-go/multierr
installは``go get -u go.uber.org/multierr``

Usage
-----

```go:main.go

import (
 "io"
 "os"

 "go.uber.org/multierr"
)

func open(paths []string) ([]io.WriteCloser, func(), error) {
 var openErr error
 files := make([]io.WriteCloser, 0, len(paths))
 close := func() {
     for _, f := range files {
         f.Close()
     }
 }
 for _, path := range paths {
     f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
     openErr = multierr.Append(openErr, err)
     if err == nil {
         files = append(files, f)
     }
 }
 return files, close, openErr
}

func main() {
	paths := []string{"./nonexists/test1.txt", "./nonexists/test2.txt", "./nonexists/test3.txt"}
	_, close, err := open(paths)
	defer close()
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s\n\n", err)

		errs := multierr.Errors(err)
		for _, openErr := range errs {
			fmt.Fprintln(os.Stderr, openErr)
		}

		fmt.Fprintf(os.Stderr, "\n%+v\n", err)
	}
}
```

```text:output
open ./nonexists/test1.txt: no such file or directory; open ./nonexists/test2.txt: no such file or directory; open ./nonexists/test3.txt: no such file or directory

open ./nonexists/test1.txt: no such file or directory
open ./nonexists/test2.txt: no such file or directory
open ./nonexists/test3.txt: no such file or directory

the following errors occurred:
 -  open ./nonexists/test1.txt: no such file or directory
 -  open ./nonexists/test2.txt: no such file or directory
 -  open ./nonexists/test3.txt: no such file or directory
```

- ``multierr.Append()``でerrorを追加
-   `multierr.Errors()` にてerror sliceに変換
-   `%+v` でformatすると, multilineで出力される

Walkthrough
-----------

### `multierr.Append()`

```go:multierr/error.go
// 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())
//       }()
func Append(left error, right error) error {
 switch {
 case left == nil:
     return right
 case right == nil:
     return left
 }

 if _, ok := right.(*multiError); !ok {
     if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) {
         // Common case where the error on the left is constantly being
         // appended to.
         errs := append(l.errors, right)
         return &multiError{errors: errs}
     } else if !ok {
         // Both errors are single errors.
         return &multiError{errors: []error{left, right}}
     }
 }

 // Either right or both, left and right, are multiErrors. Rely on usual
 // expensive logic.
 errors := [2]error{left, right}
 return fromSlice(errors[0:])
}
```
``multierr.Append()``は, 与えられた２つのerrorに応じて、そのままどちらかを返すか、``multiError``を作成して返します。
``!l.copyNeeded.Swap(true)``の条件によって、同じmultiErrorに対する2回目以降のappendが実行されないようにされています。
``errors``を``[2]error{left,right}``で初期化して、``errors[0:]``によってsliceとして``fromSlice()``に渡します。最初から``[]error{left,rigth}``としていない理由はわかりません。


### `multierr.multiError`

```go:multierr/error.go
type errorGroup interface {
 Errors() []error
}

type multiError struct {
 copyNeeded atomic.Bool
 errors     []error
}

var _ errorGroup = (*multiError)(nil)
```

```go:go.uber.org/atomic/atomic.go
import (
 "math"
 "sync/atomic"
)

// Bool is an atomic Boolean.
type Bool struct{ v uint32 }

// Swap sets the given value and returns the previous value.
func (b *Bool) Swap(new bool) bool {
 return truthy(atomic.SwapUint32(&b.v, boolToInt(new)))
}

func truthy(n uint32) bool {
 return n&1 == 1
}

func boolToInt(b bool) uint32 {
 if b {
     return 1
 }
 return 0
}
```

``multierr.multiError``は``[]error``と``go.uber.org/atomic.Bool``を保持しています。

### `multierr.fromSlice()`

```go:multierr/error.go
type inspectResult struct {
 // Number of top-level non-nil errors
 Count int

 // Total number of errors including multiErrors
 Capacity 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 multiError
 ContainsMultiError bool
}

// Inspects the given slice of errors so that we can efficiently allocate
// space for it.
func inspect(errors []error) (res inspectResult) {
 first := true
 for i, err := range errors {
     if err == nil {
         continue
     }

     res.Count++
     if first {
         first = false
         res.FirstErrorIdx = i
     }

     if merr, ok := err.(*multiError); ok {
         res.Capacity += len(merr.errors)
         res.ContainsMultiError = true
     } else {
         res.Capacity++
     }
 }
 return
}

// fromSlice converts the given list of errors into a single error.
func fromSlice(errors []error) error {
 res := inspect(errors)
 switch res.Count {
 case 0:
     return nil
 case 1:
     // only one non-nil entry
     return errors[res.FirstErrorIdx]
 case len(errors):
     if !res.ContainsMultiError {
         // already flat
         return &multiError{errors: errors}
     }
 }

 nonNilErrs := make([]error, 0, res.Capacity)
 for _, err := range errors[res.FirstErrorIdx:] {
     if err == nil {
         continue
     }

     if nested, ok := err.(*multiError); ok {
         nonNilErrs = append(nonNilErrs, nested.errors...)
     } else {
         nonNilErrs = append(nonNilErrs, err)
     }
 }

 return &multiError{errors: nonNilErrs}
}
```
``fromSlice()``は``inspect()``によって、与えられた``[]error``の中に``error``, ``nil``, ``multiError``があるかを調べます。
そして、``nil``は無視し、``multiError``はflatして保持します。

### `multiError.Error()`

```go:multierr/error.go
func (merr *multiError) Error() string {
 if merr == nil {
     return ""
 }

 buff := _bufferPool.Get().(*bytes.Buffer)
 buff.Reset()

 merr.writeSingleline(buff)

 result := buff.String()
 _bufferPool.Put(buff)
 return result
}

// _bufferPool is a pool of bytes.Buffers.
var _bufferPool = sync.Pool{
 New: func() interface{} {
     return &bytes.Buffer{}
 },
}

var (
 // Separator for single-line error messages.
 _singlelineSeparator = []byte("; ")
)

func (merr *multiError) writeSingleline(w io.Writer) {
 first := true
 for _, item := range merr.errors {
     if first {
         first = false
     } else {
         w.Write(_singlelineSeparator)
     }
     io.WriteString(w, item.Error())
 }
}
```
``multiError.Error()``は内部に保持している``error``をseparator(``; ``)で結合して出力します。出力の際に使用する``bytes.Buffer``は``sync.Pool``で再利用しています。

まとめ
-----
一つの処理の中で複数のerrorを発生しうる場合や、resouceのclose処理時などで、利用できたらと思います。``sync.Pool``で``bytes.Buffer``を使いまわすところは、いろいろなところで利用できそうです。``multiError.copyNeeded``として保持している``atomic.Bool``の役割がよく理解できませんでした。