![Go logo](img/go-logo.svg) <!-- .element style="border: none; box-shadow: none; height: 100px" --> ## Error handling --- # History -- ## Errors are values <pre><code class="hljs language-go" data-line-numbers>package foo import "errors" func Foo() error { errors.New("something went wrong") } </code></pre> <pre><code class="hljs language-go" data-line-numbers>package bar import "foo" func Bar() error { return foo.Foo() } </code></pre> -- ## What happened? <pre><code class="hljs language-go" data-line-numbers>package main import ( "fmt" "bar" ) func main() error { fmt.Println(bar.Bar()) } // Output: something went wrong (?) </code></pre> -- ## Standard library solutions -- ## Error prefixing <pre><code class="hljs language-go" data-line-numbers="6">package foo import "errors" func Foo() error { errors.New("foo: something went wrong") } </code></pre> -- ## Sentinel errors <pre><code class="hljs language-go" data-line-numbers="7">package io // ... // EOF is the error returned by Read // when no more input is available. var EOF = errors.New("EOF") </code></pre> -- ## Not enough!!! - Stack trace - Propagation - Implicit interface compatibility -- ## [github.com/pkg/errors](https://github.com/pkg/errors) - Stack trace - Additional message - Root cause -- ## [Emperror](https://github.com/goph/emperror) - Extends **github.com/pkg/errors** - Error handling tools --- # Today -- ## Go 1.12: Modules [Emperror](https://github.com/goph/emperror) became a huge dependency --- **Solution:** Split up the *module* into smaller ones -- ## New [Emperror](https://github.com/emperror/emperror) library - New [organization](https://github.com/emperror) - Vanity import URL: `emperror.dev/emperror` ```go import "emperror.dev/handler/logrus" // VS import logrus "github.com/emperror/handler-logrus" ``` -- ## Go 1.13: Errors - `Unwrap` - `As` - `Is` -- ## `Unwrap` <pre><code class="hljs language-go" data-line-numbers>type myError struct { err error } func (e myError) Error() string { return e.err.Error() } func (e myError) Unwrap() error { return e.err } func (e myError) Cause() error { return e.err } err1 := errors.New("error") err2 := &myError{err1} err3 := &myError{err2} err := errors.Unwrap(err3) // == err2 // BUT err := pkgErrors.Cause(err3) // == err1 (!!!) </code></pre> -- ## `As` <pre><code class="hljs language-go" data-line-numbers>err := pkgErrors.New("error") var stackTracer interface{ StackTrace() pkgErrors.StackTrace } if errors.As(err, &stackTracer) { st := stackTracer.StackTrace() } </code></pre> > "Assert errors for behaviour, not type" - [Dave Cheney](https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully) -- ## `Is` <pre><code class="hljs language-go" data-line-numbers>err := someFileReading() if errors.Is(err, io.EOF) { fmt.Println("help, IO error occurred") } </code></pre> -- ## Go 1.13: Errors Partial incompatibility with [github.com/pkg/errors](https://github.com/pkg/errors) Only from Go 1.13 No *stack trace* --- **Solution:** New error library -- ## `emperror.dev/errors` Drop-in replacement for `errors` and [github.com/pkg/errors](https://github.com/pkg/errors) Merged parts of [Emperror](https://github.com/emperror/emperror) into the new library Tested with replaced library test suites --- # What changed? -- ## New packages/modules | Old | New | |------------------------------------|--------------------------| | `github.com/goph/emperror` | `emperror.dev/emperror` | | `github.com/goph/emperror/handler/*handler` | `emperror.dev/handler/*` | | (no previous module) | `emperror.dev/errors` | <!-- .element style="font-size: 30px;" --> -- ## Deprecations | Old | New | |---------------------------------|----------------------------| | `emperror.Wrap` | `errors.WrapIf` | | `emperror.Wrapf` | `emperror.WrapIff` | | `emperror.With` | `errors.WithDetails` | | `emperror.WrapWith` | `errors.WrapIfWithDetails` | | `emperror.Context` | `errors.GetDetails` | | `emperror.NewMultiErrorBuilder` | `errors.Combine` | | `emperror.ForEachCause` | `errors.UnwrapEach` | | `emperror.HandlerWith` | `emperror.WithDetails` | | `emperror.HandlerWithPrefix` | (no replacement yet) | <!-- .element style="font-size: 30px;" --> -- ## New functions - `NewWithDetails` - `NewPlain` - `WithStackDepth` / `WithStackDepthIf` --- [Documentation and examples](https://godoc.org/emperror.dev/errors) --- # Examples -- ## New errors ```go // annotated with stack trace err := errors.New("something went wrong") err := errors.Errorf("something went %s", "wrong") // annotated with stack trace and details err := errors.NewWithDetails( "something went wrong", "key", "value", ) ``` -- ## Custom errors ```go type TodoNotFoundError struct{ TodoID int } func (TodoNotFoundError) Error() string { return "todo not found" } func (e TodoNotFoundError) Details() []interface{} { return []interface{}{"todo_id", e.TodoID} } // ... err := errors.WithStack(TodoNotFoundError{TodoID: 1}) ``` -- ## Custom errors ```go var e TodoNotFoundError if errors.As(err, &e) { fmt.Printf("todo id: %d", e.TodoID) } // OR e := TodoNotFoundError{TodoID: 1} if errors.Is(err, e) { fmt.Printf("todo id: %d", e.TodoID) } ``` -- ## Custom errors ```go var e interface{ NotFound() bool } if errors.As(err, &e) { fmt.Println("todo not found") } ``` -- ## Sentinel errors ```go // create an error without stack trace var ErrNotFound = errors.NewPlain("not found") // ... // annotate the error with stack trace err := errors.WithStack(ErrNotFound) // ... // check for error equality if errors.Is(err, ErrNotFound) { fmt.Println(err) } ``` -- ## Error wrapping ```go var err error // *If functions only annotate err with a stack trace // if there isn't one already in err's chain err = errors.WrapIf(err, "error passed through here") // add key-value pairs to err // access them with errors.GetDetails err = errors.WithDetails(err, "key", "value") ``` -- ## Multi errors ```go var errs []error for !exitCondition { err := funcThatErrors() errs = append(errs, err) } // combines several errors into a single one // nil values are removed err := errors.Combine(errs...) ``` --- ## Further reading https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully https://8thlight.com/blog/kyle-krull/2018/08/13/exploring-error-handling-patterns-in-go.html https://banzaicloud.com/blog/error-handling-go/ -- ## Go 2 proposals https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md https://go.googlesource.com/proposal/+/master/design/go2draft-error-values-overview.md