Avoid allocating a new slice on every Append (#9)

Currently, appending a non-nil error to a multierror always copies the underlying slice, since there may be multiple goroutines appending to the same multierr,
```go
merr := multierr.Append(errors.New("initial"), errors.New("multierr"))
go func() {
  err1 = multierr.Append(merr, errors.New("1"))
  [..]
}()
go func() {
  err2 = multierr.Append(merr, errors.New("2"))
  [..]
}()
```

However, the common use case is a standard for loop:
```go
var merr error
for _, v := range values {
  merr = multierr.Append(merr, processValue(v))
}
return merr
```

Since there is only a single resulting slice, we don't need to create a full copy on every `Append`. Instead, we track whether we have modified the underlying slice from `Append` already using an atomic boolean, and only create a copy if the underlying slice has been modified.

diff --git a/error.go b/error.go
index a247250..c6a9b75 100644
--- a/error.go
+++ b/error.go
@@ -27,6 +27,8 @@
 	"io"
 	"strings"
 	"sync"
+
+	"go.uber.org/atomic"
 )
 
 var (
@@ -68,13 +70,12 @@
 //
 // 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 []error
-
-func (merr multiError) String() string {
-	return merr.Error()
+type multiError struct {
+	copyNeeded atomic.Bool
+	errors     []error
 }
 
-func (merr multiError) Error() string {
+func (merr *multiError) Error() string {
 	buff := _bufferPool.Get().(*bytes.Buffer)
 	buff.Reset()
 
@@ -85,7 +86,7 @@
 	return result
 }
 
-func (merr multiError) Format(f fmt.State, c rune) {
+func (merr *multiError) Format(f fmt.State, c rune) {
 	if c == 'v' && f.Flag('+') {
 		merr.writeMultiline(f)
 	} else {
@@ -93,9 +94,9 @@
 	}
 }
 
-func (merr multiError) writeSingleline(w io.Writer) {
+func (merr *multiError) writeSingleline(w io.Writer) {
 	first := true
-	for _, item := range merr {
+	for _, item := range merr.errors {
 		if first {
 			first = false
 		} else {
@@ -105,9 +106,9 @@
 	}
 }
 
-func (merr multiError) writeMultiline(w io.Writer) {
+func (merr *multiError) writeMultiline(w io.Writer) {
 	w.Write(_multilinePrefix)
-	for _, item := range merr {
+	for _, item := range merr.errors {
 		w.Write(_multilineSeparator)
 		writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item))
 	}
@@ -164,8 +165,8 @@
 			res.FirstErrorIdx = i
 		}
 
-		if merr, ok := err.(multiError); ok {
-			res.Capacity += len(merr)
+		if merr, ok := err.(*multiError); ok {
+			res.Capacity += len(merr.errors)
 			res.ContainsMultiError = true
 		} else {
 			res.Capacity++
@@ -186,23 +187,24 @@
 	case len(errors):
 		if !res.ContainsMultiError {
 			// already flat
-			return multiError(errors)
+			return &multiError{errors: errors}
 		}
 	}
 
-	merr := make(multiError, 0, res.Capacity)
+	nonNilErrs := make([]error, 0, res.Capacity)
 	for _, err := range errors[res.FirstErrorIdx:] {
 		if err == nil {
 			continue
 		}
 
-		if nested, ok := err.(multiError); ok {
-			merr = append(merr, nested...)
+		if nested, ok := err.(*multiError); ok {
+			nonNilErrs = append(nonNilErrs, nested.errors...)
 		} else {
-			merr = append(merr, err)
+			nonNilErrs = append(nonNilErrs, err)
 		}
 	}
-	return merr
+
+	return &multiError{errors: nonNilErrs}
 }
 
 // Combine combines the passed errors into a single error.
@@ -263,10 +265,15 @@
 		return left
 	}
 
-	if _, ok := right.(multiError); !ok {
-		if _, ok := left.(multiError); !ok {
+	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{left, right}
+			return &multiError{errors: []error{left, right}}
 		}
 	}
 
diff --git a/error_test.go b/error_test.go
index e54fcd7..e05b905 100644
--- a/error_test.go
+++ b/error_test.go
@@ -24,6 +24,7 @@
 	"errors"
 	"fmt"
 	"io"
+	"sync"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -54,6 +55,10 @@
 	return errs
 }
 
+func newMultiErr(errors ...error) error {
+	return &multiError{errors: errors}
+}
+
 func TestCombine(t *testing.T) {
 	tests := []struct {
 		giveErrors     []error
@@ -73,15 +78,15 @@
 			giveErrors: []error{
 				errors.New("foo"),
 				nil,
-				multiError{
+				newMultiErr(
 					errors.New("bar"),
-				},
+				),
 				nil,
 			},
-			wantError: multiError{
+			wantError: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
-			},
+			),
 			wantMultiline: "the following errors occurred:\n" +
 				" -  foo\n" +
 				" -  bar",
@@ -90,14 +95,14 @@
 		{
 			giveErrors: []error{
 				errors.New("foo"),
-				multiError{
+				newMultiErr(
 					errors.New("bar"),
-				},
+				),
 			},
-			wantError: multiError{
+			wantError: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
-			},
+			),
 			wantMultiline: "the following errors occurred:\n" +
 				" -  foo\n" +
 				" -  bar",
@@ -114,10 +119,10 @@
 				errors.New("foo"),
 				errors.New("bar"),
 			},
-			wantError: multiError{
+			wantError: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
-			},
+			),
 			wantMultiline: "the following errors occurred:\n" +
 				" -  foo\n" +
 				" -  bar",
@@ -129,11 +134,11 @@
 				errors.New("multi\n  line\nerror message"),
 				errors.New("single line error message"),
 			},
-			wantError: multiError{
+			wantError: newMultiErr(
 				errors.New("great sadness"),
 				errors.New("multi\n  line\nerror message"),
 				errors.New("single line error message"),
-			},
+			),
 			wantMultiline: "the following errors occurred:\n" +
 				" -  great sadness\n" +
 				" -  multi\n" +
@@ -147,18 +152,18 @@
 		{
 			giveErrors: []error{
 				errors.New("foo"),
-				multiError{
+				newMultiErr(
 					errors.New("bar"),
 					errors.New("baz"),
-				},
+				),
 				errors.New("qux"),
 			},
-			wantError: multiError{
+			wantError: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
 				errors.New("baz"),
 				errors.New("qux"),
-			},
+			),
 			wantMultiline: "the following errors occurred:\n" +
 				" -  foo\n" +
 				" -  bar\n" +
@@ -170,15 +175,15 @@
 			giveErrors: []error{
 				errors.New("foo"),
 				nil,
-				multiError{
+				newMultiErr(
 					errors.New("bar"),
-				},
+				),
 				nil,
 			},
-			wantError: multiError{
+			wantError: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
-			},
+			),
 			wantMultiline: "the following errors occurred:\n" +
 				" -  foo\n" +
 				" -  bar",
@@ -187,14 +192,14 @@
 		{
 			giveErrors: []error{
 				errors.New("foo"),
-				multiError{
+				newMultiErr(
 					errors.New("bar"),
-				},
+				),
 			},
-			wantError: multiError{
+			wantError: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
-			},
+			),
 			wantMultiline: "the following errors occurred:\n" +
 				" -  foo\n" +
 				" -  bar",
@@ -206,11 +211,11 @@
 				richFormatError{},
 				errors.New("bar"),
 			},
-			wantError: multiError{
+			wantError: newMultiErr(
 				errors.New("foo"),
 				richFormatError{},
 				errors.New("bar"),
-			},
+			),
 			wantMultiline: "the following errors occurred:\n" +
 				" -  foo\n" +
 				" -  multiline\n" +
@@ -277,46 +282,46 @@
 		{
 			left:  errors.New("foo"),
 			right: errors.New("bar"),
-			want: multiError{
+			want: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
-			},
+			),
 		},
 		{
-			left: multiError{
+			left: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
-			},
+			),
 			right: errors.New("baz"),
-			want: multiError{
+			want: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
 				errors.New("baz"),
-			},
+			),
 		},
 		{
 			left: errors.New("baz"),
-			right: multiError{
+			right: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
-			},
-			want: multiError{
+			),
+			want: newMultiErr(
 				errors.New("baz"),
 				errors.New("foo"),
 				errors.New("bar"),
-			},
+			),
 		},
 		{
-			left: multiError{
+			left: newMultiErr(
 				errors.New("foo"),
-			},
-			right: multiError{
+			),
+			right: newMultiErr(
 				errors.New("bar"),
-			},
-			want: multiError{
+			),
+			want: newMultiErr(
 				errors.New("foo"),
 				errors.New("bar"),
-			},
+			),
 		},
 	}
 
@@ -325,20 +330,40 @@
 	}
 }
 
-func TestAppendDoesNotModify(t *testing.T) {
-	createInitial := func() error {
-		// Create a multiError that has capacity for more errors so Append will
-		// modify the underlying array that may be shared.
-		return appendN(nil, errors.New("initial"), 50)
-	}
+func createMultiErrWithCapacity() error {
+	// Create a multiError that has capacity for more errors so Append will
+	// modify the underlying array that may be shared.
+	return appendN(nil, errors.New("append"), 50)
+}
 
-	initial := createInitial()
+func TestAppendDoesNotModify(t *testing.T) {
+	initial := createMultiErrWithCapacity()
 	err1 := Append(initial, errors.New("err1"))
 	err2 := Append(initial, errors.New("err2"))
 
-	// initial should not have been modified.
-	assert.Equal(t, createInitial(), initial, "Initial should not be modified")
+	// Make sure the error messages match, since we do modify the copyNeeded
+	// atomic, the values cannot be compared.
+	assert.EqualError(t, initial, createMultiErrWithCapacity().Error(), "Initial should not be modified")
 
-	assert.Equal(t, Append(createInitial(), errors.New("err1")), err1)
-	assert.Equal(t, Append(createInitial(), errors.New("err2")), err2)
+	assert.EqualError(t, err1, Append(createMultiErrWithCapacity(), errors.New("err1")).Error())
+	assert.EqualError(t, err2, Append(createMultiErrWithCapacity(), errors.New("err2")).Error())
+}
+
+func TestAppendRace(t *testing.T) {
+	initial := createMultiErrWithCapacity()
+
+	var wg sync.WaitGroup
+	for i := 0; i < 10; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+
+			err := initial
+			for j := 0; j < 10; j++ {
+				err = Append(err, errors.New("err"))
+			}
+		}()
+	}
+
+	wg.Wait()
 }
diff --git a/glide.lock b/glide.lock
index 15c5fa4..f9ea94c 100644
--- a/glide.lock
+++ b/glide.lock
@@ -1,16 +1,19 @@
-hash: cb430176250bcfe8bafd93aa48d285fa35b08af59b5f9971dcf553a1d6c236ec
-updated: 2017-03-17T16:22:25.024129734-07:00
-imports: []
+hash: b53b5e9a84b9cb3cc4b2d0499e23da2feca1eec318ce9bb717ecf35bf24bf221
+updated: 2017-04-10T13:34:45.671678062-07:00
+imports:
+- name: go.uber.org/atomic
+  version: 3b8db5e93c4c02efbc313e17b2e796b0914a01fb
 testImports:
 - name: github.com/davecgh/go-spew
-  version: 346938d642f2ec3594ed81d874461961cd0faa76
+  version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
   subpackages:
   - spew
 - name: github.com/pmezard/go-difflib
-  version: 792786c7400a136282c1664665ae0a8db921c6c2
+  version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
   subpackages:
   - difflib
 - name: github.com/stretchr/testify
-  version: 4d4bfba8f1d1027c4fdbe371823030df51419987
+  version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
   subpackages:
   - assert
+  - require
diff --git a/glide.yaml b/glide.yaml
index 67951f5..6ef084e 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -1,5 +1,7 @@
 package: go.uber.org/multierr
-import: []
+import:
+- package: go.uber.org/atomic
+  version: ^1
 testImport:
 - package: github.com/stretchr/testify
   subpackages: