Initial import (#1)

This is the initial import of the `multierr` library. The library
intends to provide a single common implementation of the `multierr`
type.

The implementation is slightly more complex than you would expect
because it aims to be zero-alloc for the hot-path (no errors or no
nested multierrors).

diff --git a/.deleteme b/.deleteme
deleted file mode 100644
index e69de29..0000000
--- a/.deleteme
+++ /dev/null
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..61ead86
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/vendor
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..858e024
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2017 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cd4e5d1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,25 @@
+multierr
+========
+
+Easy grouping of one or more errors into a single error.
+
+Installation
+------------
+
+    go get -u go.uber.org/multierr
+
+Usage
+-----
+
+TODO
+
+Status
+------
+
+Stable
+
+-------------------------------------------------------------------------------
+
+Released under the [MIT License].
+
+  [MIT License]: LICENSE.txt
diff --git a/error.go b/error.go
new file mode 100644
index 0000000..66b1d50
--- /dev/null
+++ b/error.go
@@ -0,0 +1,280 @@
+// Copyright (c) 2017 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 // import "go.uber.org/multierr"
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"strings"
+	"sync"
+)
+
+var (
+	// Separator for single-line error messages.
+	_singlelineSeparator = []byte("; ")
+
+	_newline = []byte("\n")
+
+	// 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{}
+	},
+}
+
+// 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 []error
+
+func (me multiError) String() string {
+	return me.Error()
+}
+
+func (me multiError) Error() string {
+	buff := _bufferPool.Get().(*bytes.Buffer)
+	buff.Reset()
+
+	me.writeSingleline(buff)
+
+	result := buff.String()
+	_bufferPool.Put(buff)
+	return result
+}
+
+func (me multiError) Format(f fmt.State, c rune) {
+	if c == 'v' && f.Flag('+') {
+		me.writeMultiline(f)
+	} else {
+		me.writeSingleline(f)
+	}
+}
+
+func (me multiError) writeSingleline(w io.Writer) {
+	first := true
+	for _, item := range me {
+		if first {
+			first = false
+		} else {
+			w.Write(_singlelineSeparator)
+		}
+		io.WriteString(w, item.Error())
+	}
+}
+
+func (me multiError) writeMultiline(w io.Writer) {
+	w.Write(_multilinePrefix)
+	for _, item := range me {
+		w.Write(_multilineSeparator)
+		writePrefixLine(w, _multilineIndent, item.Error())
+	}
+}
+
+// Writes s to the writer with the given prefix added before each line after
+// the first.
+func writePrefixLine(w io.Writer, prefix []byte, s string) {
+	first := true
+	for len(s) > 0 {
+		if first {
+			first = false
+		} else {
+			w.Write(prefix)
+		}
+
+		idx := strings.IndexByte(s, '\n')
+		if idx < 0 {
+			idx = len(s) - 1
+		}
+
+		io.WriteString(w, s[:idx+1])
+		s = s[idx+1:]
+	}
+}
+
+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 me, ok := err.(multiError); ok {
+			res.Capacity += len(me)
+			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)
+		}
+	}
+
+	me := make(multiError, 0, res.Capacity)
+	for _, err := range errors[res.FirstErrorIdx:] {
+		if err == nil {
+			continue
+		}
+
+		if nested, ok := err.(multiError); ok {
+			me = append(me, nested...)
+		} else {
+			me = append(me, err)
+		}
+	}
+	return me
+}
+
+// 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 Combine(errors ...error) error {
+	return fromSlice(errors)
+}
+
+// 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())
+//
+// This may 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 {
+			// Common case where the error on the left is constantly being
+			// appended to.
+			return append(l, right)
+		}
+
+		// Both errors are single errors.
+		return multiError{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:])
+}
diff --git a/error_test.go b/error_test.go
new file mode 100644
index 0000000..4c72c45
--- /dev/null
+++ b/error_test.go
@@ -0,0 +1,262 @@
+package multierr
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCombine(t *testing.T) {
+	tests := []struct {
+		giveErrors     []error
+		wantError      error
+		wantMultiline  string
+		wantSingleline string
+	}{
+		{
+			giveErrors: nil,
+			wantError:  nil,
+		},
+		{
+			giveErrors: []error{},
+			wantError:  nil,
+		},
+		{
+			giveErrors: []error{
+				errors.New("foo"),
+				nil,
+				multiError{
+					errors.New("bar"),
+				},
+				nil,
+			},
+			wantError: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+			wantMultiline: "the following errors occurred:\n" +
+				" -  foo\n" +
+				" -  bar",
+			wantSingleline: "foo; bar",
+		},
+		{
+			giveErrors: []error{
+				errors.New("foo"),
+				multiError{
+					errors.New("bar"),
+				},
+			},
+			wantError: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+			wantMultiline: "the following errors occurred:\n" +
+				" -  foo\n" +
+				" -  bar",
+			wantSingleline: "foo; bar",
+		},
+		{
+			giveErrors:     []error{errors.New("great sadness")},
+			wantError:      errors.New("great sadness"),
+			wantMultiline:  "great sadness",
+			wantSingleline: "great sadness",
+		},
+		{
+			giveErrors: []error{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+			wantError: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+			wantMultiline: "the following errors occurred:\n" +
+				" -  foo\n" +
+				" -  bar",
+			wantSingleline: "foo; bar",
+		},
+		{
+			giveErrors: []error{
+				errors.New("great sadness"),
+				errors.New("multi\n  line\nerror message"),
+				errors.New("single line error message"),
+			},
+			wantError: multiError{
+				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" +
+				"      line\n" +
+				"    error message\n" +
+				" -  single line error message",
+			wantSingleline: "great sadness; " +
+				"multi\n  line\nerror message; " +
+				"single line error message",
+		},
+		{
+			giveErrors: []error{
+				errors.New("foo"),
+				multiError{
+					errors.New("bar"),
+					errors.New("baz"),
+				},
+				errors.New("qux"),
+			},
+			wantError: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+				errors.New("baz"),
+				errors.New("qux"),
+			},
+			wantMultiline: "the following errors occurred:\n" +
+				" -  foo\n" +
+				" -  bar\n" +
+				" -  baz\n" +
+				" -  qux",
+			wantSingleline: "foo; bar; baz; qux",
+		},
+		{
+			giveErrors: []error{
+				errors.New("foo"),
+				nil,
+				multiError{
+					errors.New("bar"),
+				},
+				nil,
+			},
+			wantError: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+			wantMultiline: "the following errors occurred:\n" +
+				" -  foo\n" +
+				" -  bar",
+			wantSingleline: "foo; bar",
+		},
+		{
+			giveErrors: []error{
+				errors.New("foo"),
+				multiError{
+					errors.New("bar"),
+				},
+			},
+			wantError: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+			wantMultiline: "the following errors occurred:\n" +
+				" -  foo\n" +
+				" -  bar",
+			wantSingleline: "foo; bar",
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprint(i), func(t *testing.T) {
+			err := Combine(tt.giveErrors...)
+			require.Equal(t, tt.wantError, err)
+
+			if tt.wantMultiline != "" {
+				assert.Equal(t, tt.wantMultiline, fmt.Sprintf("%+v", err))
+			}
+
+			if tt.wantSingleline != "" {
+				assert.Equal(t, tt.wantSingleline, err.Error())
+				if s, ok := err.(fmt.Stringer); ok {
+					assert.Equal(t, tt.wantSingleline, s.String())
+				}
+				assert.Equal(t, tt.wantSingleline, fmt.Sprintf("%v", err))
+			}
+		})
+	}
+}
+
+func TestCombineDoesNotModifySlice(t *testing.T) {
+	errors := []error{
+		errors.New("foo"),
+		nil,
+		errors.New("bar"),
+	}
+
+	assert.NotNil(t, Combine(errors...))
+	assert.Len(t, errors, 3)
+	assert.Nil(t, errors[1], 3)
+}
+
+func TestAppend(t *testing.T) {
+	tests := []struct {
+		left  error
+		right error
+		want  error
+	}{
+		{
+			left:  nil,
+			right: nil,
+			want:  nil,
+		},
+		{
+			left:  nil,
+			right: errors.New("great sadness"),
+			want:  errors.New("great sadness"),
+		},
+		{
+			left:  errors.New("great sadness"),
+			right: nil,
+			want:  errors.New("great sadness"),
+		},
+		{
+			left:  errors.New("foo"),
+			right: errors.New("bar"),
+			want: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+		},
+		{
+			left: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+			right: errors.New("baz"),
+			want: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+				errors.New("baz"),
+			},
+		},
+		{
+			left: errors.New("baz"),
+			right: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+			want: multiError{
+				errors.New("baz"),
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+		},
+		{
+			left: multiError{
+				errors.New("foo"),
+			},
+			right: multiError{
+				errors.New("bar"),
+			},
+			want: multiError{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		assert.Equal(t, tt.want, Append(tt.left, tt.right))
+	}
+}
diff --git a/glide.lock b/glide.lock
new file mode 100644
index 0000000..15c5fa4
--- /dev/null
+++ b/glide.lock
@@ -0,0 +1,16 @@
+hash: cb430176250bcfe8bafd93aa48d285fa35b08af59b5f9971dcf553a1d6c236ec
+updated: 2017-03-17T16:22:25.024129734-07:00
+imports: []
+testImports:
+- name: github.com/davecgh/go-spew
+  version: 346938d642f2ec3594ed81d874461961cd0faa76
+  subpackages:
+  - spew
+- name: github.com/pmezard/go-difflib
+  version: 792786c7400a136282c1664665ae0a8db921c6c2
+  subpackages:
+  - difflib
+- name: github.com/stretchr/testify
+  version: 4d4bfba8f1d1027c4fdbe371823030df51419987
+  subpackages:
+  - assert
diff --git a/glide.yaml b/glide.yaml
new file mode 100644
index 0000000..67951f5
--- /dev/null
+++ b/glide.yaml
@@ -0,0 +1,6 @@
+package: go.uber.org/multierr
+import: []
+testImport:
+- package: github.com/stretchr/testify
+  subpackages:
+  - assert