Support extracting underlying error list (#19)

This change adds an `Errors(err) []error` function which returns the
underlying list of errors. Additionally, it amends the contract for
returned errors that they MAY implement a specific interface.

This is needed for Zap integration as per discussion in #6.

Resolves #10.

diff --git a/error.go b/error.go
index a16cbb6..de6ce47 100644
--- a/error.go
+++ b/error.go
@@ -20,6 +20,8 @@
 
 // Package multierr allows combining one or more errors together.
 //
+// Overview
+//
 // Errors can be combined with the use of the Combine function.
 //
 // 	multierr.Combine(
@@ -46,6 +48,41 @@
 // 		}()
 // 		// ...
 // 	}
+//
+// 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:")
+// 	}
+//
+// 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 (
@@ -90,6 +127,43 @@
 	},
 }
 
+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, the returned slice is empty.
+//
+// 	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 Errors(err error) []error {
+	if err == nil {
+		return nil
+	}
+
+	// Note that we're casting to multiError, not errorGroup. Our contract is
+	// that returned errors MAY implement errorGroup. Errors, however, only
+	// has special behavior for multierr-specific error objects.
+	//
+	// This behavior can be expanded in the future but I think it's prudent to
+	// start with as little as possible in terms of contract and possibility
+	// of misuse.
+	eg, ok := err.(*multiError)
+	if !ok {
+		return []error{err}
+	}
+
+	errors := eg.Errors()
+	result := make([]error, len(errors))
+	copy(result, errors)
+	return result
+}
+
 // multiError is an error that holds one or more errors.
 //
 // An instance of this is guaranteed to be non-empty and flattened. That is,
@@ -102,6 +176,18 @@
 	errors     []error
 }
 
+var _ errorGroup = (*multiError)(nil)
+
+// Errors returns the list of underlying errors.
+//
+// This slice MUST NOT be modified.
+func (merr *multiError) Errors() []error {
+	if merr == nil {
+		return nil
+	}
+	return merr.errors
+}
+
 func (merr *multiError) Error() string {
 	if merr == nil {
 		return ""
@@ -280,8 +366,8 @@
 //
 // 	err = multierr.Append(reader.Close(), writer.Close())
 //
-// This may be used to record failure of deferred operations without losing
-// information about the original error.
+// 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()
diff --git a/error_test.go b/error_test.go
index 053859b..9384ff5 100644
--- a/error_test.go
+++ b/error_test.go
@@ -345,6 +345,109 @@
 	}
 }
 
+type notMultiErr struct{}
+
+var _ errorGroup = notMultiErr{}
+
+func (notMultiErr) Error() string {
+	return "great sadness"
+}
+
+func (notMultiErr) Errors() []error {
+	return []error{errors.New("great sadness")}
+}
+
+func TestErrors(t *testing.T) {
+	tests := []struct {
+		give error
+		want []error
+
+		// Don't attempt to cast to errorGroup or *multiError
+		dontCast bool
+	}{
+		{dontCast: true}, // nil
+		{
+			give:     errors.New("hi"),
+			want:     []error{errors.New("hi")},
+			dontCast: true,
+		},
+		{
+			// We don't yet support non-multierr errors.
+			give:     notMultiErr{},
+			want:     []error{notMultiErr{}},
+			dontCast: true,
+		},
+		{
+			give: Combine(
+				errors.New("foo"),
+				errors.New("bar"),
+			),
+			want: []error{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+		},
+		{
+			give: Append(
+				errors.New("foo"),
+				errors.New("bar"),
+			),
+			want: []error{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+		},
+		{
+			give: Append(
+				errors.New("foo"),
+				Combine(
+					errors.New("bar"),
+				),
+			),
+			want: []error{
+				errors.New("foo"),
+				errors.New("bar"),
+			},
+		},
+		{
+			give: Combine(
+				errors.New("foo"),
+				Append(
+					errors.New("bar"),
+					errors.New("baz"),
+				),
+				errors.New("qux"),
+			),
+			want: []error{
+				errors.New("foo"),
+				errors.New("bar"),
+				errors.New("baz"),
+				errors.New("qux"),
+			},
+		},
+	}
+
+	for i, tt := range tests {
+		t.Run(fmt.Sprint(i), func(t *testing.T) {
+			t.Run("Errors()", func(t *testing.T) {
+				require.Equal(t, tt.want, Errors(tt.give))
+			})
+
+			if tt.dontCast {
+				return
+			}
+
+			t.Run("multiError", func(t *testing.T) {
+				require.Equal(t, tt.want, tt.give.(*multiError).Errors())
+			})
+
+			t.Run("errorGroup", func(t *testing.T) {
+				require.Equal(t, tt.want, tt.give.(errorGroup).Errors())
+			})
+		})
+	}
+}
+
 func createMultiErrWithCapacity() error {
 	// Create a multiError that has capacity for more errors so Append will
 	// modify the underlying array that may be shared.
@@ -383,10 +486,26 @@
 	wg.Wait()
 }
 
+func TestErrorsSliceIsImmutable(t *testing.T) {
+	err1 := errors.New("err1")
+	err2 := errors.New("err2")
+
+	err := Append(err1, err2)
+	gotErrors := Errors(err)
+	require.Equal(t, []error{err1, err2}, gotErrors, "errors must match")
+
+	gotErrors[0] = nil
+	gotErrors[1] = errors.New("err3")
+
+	require.Equal(t, []error{err1, err2}, Errors(err),
+		"errors must match after modification")
+}
+
 func TestNilMultierror(t *testing.T) {
 	// For safety, all operations on multiError should be safe even if it is
 	// nil.
 	var err *multiError
 
 	require.Empty(t, err.Error())
+	require.Empty(t, err.Errors())
 }