├── .travis.yml ├── LICENSE ├── README.md ├── defer.go ├── defer_test.go ├── doc.go ├── errc.go ├── errc_test.go ├── example_test.go ├── handler.go └── handler_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go_import_path: github.com/mpvl/errc 3 | go: 4 | - 1.9.x 5 | - 1.8.x 6 | - tip 7 | 8 | before_install: 9 | - go get -t -v ./... 10 | 11 | script: 12 | - go test -race -coverprofile=coverage.txt -covermode=atomic 13 | 14 | after_success: 15 | - bash <(curl -s https://codecov.io/bash) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # errc [![GoDoc](https://godoc.org/github.com/mpvl/errc?status.svg)](http://godoc.org/github.com/mpvl/errc) [![Travis-CI](https://travis-ci.org/mpvl/errc.svg)](https://travis-ci.org/mpvl/errc) [![Report card](https://goreportcard.com/badge/github.com/mpvl/errc)](https://goreportcard.com/report/github.com/mpvl/errc) [![codecov](https://codecov.io/gh/mpvl/errc/branch/master/graph/badge.svg)](https://codecov.io/gh/mpvl/errc) 2 | 3 | 4 | Package `errc` simplifies error and defer handling. 5 | 6 | ```go get github.com/mpvl/errc``` 7 | 8 | Also note the sibling package `go get github.com/mpvl/errd`. Package `errc` 9 | probably looks more like a language feature would look like. Package `errd` 10 | however is a bit safer to use as well as a bit faster. 11 | 12 | 13 | 14 | ## Overview 15 | 16 | Package `errc` is a _burner package_, a proof-of-concept to analyze how to 17 | improve error handling for future iterations of Go. The idiomatic way to 18 | handle errors in Go looks like: 19 | 20 | ``` 21 | func foo() (err error) { 22 | r, err := getReader() 23 | if err != nil { 24 | return err 25 | } 26 | defer r.Close() 27 | // etc. 28 | ``` 29 | 30 | The implied promise of simplicity of this pattern, though a bit verbose, often 31 | does not hold. 32 | Take the following example: 33 | 34 | 35 | ```go 36 | func writeToGS(ctx context.Context, bucket, dst string, r io.Reader) (err error) { 37 | client, err := storage.NewClient(ctx) 38 | if err != nil { 39 | return err 40 | } 41 | defer client.Close() 42 | 43 | w := client.Bucket(bucket).Object(dst).NewWriter(ctx) 44 | err = errPanicking 45 | defer func() { 46 | if err != nil { 47 | _ = w.CloseWithError(err) 48 | } else { 49 | err = w.Close() 50 | } 51 | } 52 | _, err = io.Copy(w, r) { 53 | return err 54 | } 55 | ``` 56 | 57 | This function atomically writes the contents of an io.Reader to a Google Cloud 58 | Storage file. 59 | It ensures the following: 60 | 1. An error resulting from closing `w` is returned if there wasn't any error already 61 | 2. In case of a panic, neither Close() nor CloseWithError(nil) is called. 62 | 63 | The first condition is necessary to ensure any retry logic will know the file 64 | was not successfully written. The second condition ensures no partial file is 65 | written in case of a panic. A panic may occur, for instance, when the server is 66 | killed by a cluster manager because it uses too much memory. 67 | 68 | Using package `errc`, the same is achieved by: 69 | 70 | ```go 71 | func writeToGS(ctx context.Context, bucket, dst, src string) (err error) { 72 | e := errc.Catch(&err) 73 | defer e.Handle() 74 | 75 | client, err := storage.NewClient(ctx) 76 | e.Must(err) 77 | e.Defer(client.Close, errc.Discard) 78 | 79 | w := client.Bucket(bucket).Object(dst).NewWriter(ctx) 80 | e.Defer(w.CloseWithError) 81 | 82 | _, err = io.Copy(w, r) 83 | e.Must(err) 84 | } 85 | ``` 86 | 87 | In this case, the above guarantees are met by applying the idiomatic 88 | check-and-defer pattern. 89 | The handling of errors around panics, `Must` and `Defer` is such that 90 | applying the check-and-defer pattern yields the correct results without much 91 | further thought. 92 | 93 | 94 | ## Error Handlers 95 | 96 | Package `errc` defines a Handler type to allow inline processing of errors. 97 | 98 | Suppose we want to use `github.com/pkg/errors` to decorate errors. 99 | A simple handler can be defined as: 100 | 101 | ```go 102 | type msg string 103 | 104 | func (m msg) Handle(s errc.State, err error) error { 105 | return errors.WithMessage(err, string(m)) 106 | } 107 | ``` 108 | 109 | This handler can then be used as follows: 110 | 111 | ```go 112 | func writeToGS(ctx context.Context, bucket, dst, src string) error { 113 | e := errc.Catch(&err) 114 | defer e.Handle() 115 | 116 | client, err := storage.NewClient(ctx) 117 | e.Must(err, msg("creating client failed")) 118 | e.Defer(client.Close, errc.Discard) 119 | 120 | w := client.Bucket(bucket).Object(dst).NewWriter(ctx) 121 | e.Defer(w.CloseWithError) 122 | 123 | _, err = io.Copy(w, r) 124 | e.Must(err, msg("copy failed")) 125 | return nil 126 | } 127 | ``` 128 | 129 | It is also possible to pass a default Handler to the Catch function, which will 130 | be applied if no Handler is given at the point of detection. 131 | 132 | ## Principles 133 | 134 | As said, `errc` is a "burner package". 135 | The goal is to improve error handling focussing on semantics first, rather than 136 | considering syntax first. 137 | 138 | The main requirements for error handling addressed by `errc` are: 139 | - errors are and remain values 140 | - Make it easy to decorate errors with additional information 141 | (and play nice with packages like `github.com/pkg/errors`). 142 | - Using an idiomatic way to handling errors should typically result in 143 | correct behavior. 144 | 145 | 146 | ## Error funnel 147 | 148 | The main `errc` concept is that of an error funnel: a single variable associated 149 | with each function in which the current error state is recorded. 150 | It is very much like having a named error return argument in which to record 151 | all errors, but ensuring that the following holds: 152 | 153 | - there is a single error variable, 154 | - an error detected by a call to `Must` or `Defer` will only be recorded in 155 | the error variable if the error variable is `nil`, 156 | - some processing is allowed unconditionally for any error that is detected, 157 | - if a panic occurs, the current error variable will be overwritten by 158 | a wrapped panic error, and 159 | - it is still possible to override any previous error value by explicitly 160 | writing to the error variable. 161 | 162 | ## Errors versus Panics 163 | 164 | One could classify error values as recoverable errors while panics are 165 | unrecoverable errors. 166 | In practice things are a bit more subtle. Cleanup code that is called through 167 | defers is still called after a panic. 168 | Although easily ignored, it may be important for such code to consider a panic 169 | as being in an erroring state. 170 | However, by default in Go panic and errors are treated completely separately. 171 | Package `errc` preserves panic semantics, while also treating panics as an 172 | error. 173 | More specifically, a panic will keep unwinding until an explicit recover, while 174 | at the same time it assigns a panic-related error to 175 | the error variable to communicate that the function is currently in an erroring 176 | state. 177 | 178 | ## How it works 179 | 180 | Package `errc` uses Go's `panic` and `recover` mechanism to force the exit from 181 | `Run` if an error is encountered. 182 | On top of that, package `errc` manages its own defer state, which is 183 | necessary to properly interweave error and defer handling. 184 | 185 | 186 | ## Performance 187 | 188 | Package `errc` adds a defer block to do all its management. 189 | If the original code only does error checking, this is a relatively 190 | big price to pay. 191 | If the original code already does a defer, the damage is limited. 192 | If the original code uses multiple defers, package `errc` may even be faster. 193 | 194 | Passing string-type error handlers, like in the example on error handlers, 195 | causes an allocation. 196 | However, in 1.9 this special case does not incur noticeable overhead over 197 | passing a pre-allocated handler. 198 | 199 | 200 | ## Caveat Emptor 201 | 202 | As `errc` uses `defer`, it does not work across goroutine boundaries. 203 | In general, it is advisable not to pass an `errc.Catcher` value as an argument 204 | to any function call. 205 | 206 | 207 | ## What's next 208 | Package `errc` is about exploring better ways and semantics for handling errors 209 | and defers. The main goal here is to come up with a good improvement for Go 2. 210 | 211 | -------------------------------------------------------------------------------- /defer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package errc 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "io" 11 | "sync" 12 | ) 13 | 14 | // A closerWithError is an io.Closer that also implements CloseWithError. 15 | type closerWithError interface { 16 | io.Closer 17 | CloseWithError(error) error 18 | } 19 | 20 | type deferData struct { 21 | x interface{} 22 | f deferFunc 23 | } 24 | 25 | // TODO: DeferFunc is much faster than Defer, as it avoids an allocation in many 26 | // cases. Add it back if users need this performance. 27 | 28 | // A DeferFunc is used to call cleanup code for x at defer time. 29 | type deferFunc func(s State, x interface{}) error 30 | 31 | // DeferFunc calls f at the end of a Run with x as its argument. 32 | // 33 | // If f returns an error it will be passed to the error handlers. 34 | // 35 | // DeferFunc can be used to avoid the allocation typically incurred 36 | // with Defer. 37 | func (e *Catcher) deferFunc(x interface{}, f deferFunc, h ...Handler) { 38 | if f == nil { 39 | panic(errNilFunc) 40 | } 41 | for i := len(h) - 1; i >= 0; i-- { 42 | e.deferred = append(e.deferred, deferData{h[i], nil}) 43 | } 44 | e.deferred = append(e.deferred, deferData{x, f}) 45 | } 46 | 47 | var errNilFunc = errors.New("errd: nil DeferFunc") 48 | 49 | var ( 50 | // Close calls x.Close(). 51 | close deferFunc = closeFunc 52 | 53 | // CloseWithError calls x.CloseWithError(). 54 | closeWithError deferFunc = closeWithErrorFunc 55 | 56 | // Unlock calls x.Unlock(). 57 | unlock deferFunc = unlockFunc 58 | ) 59 | 60 | func closeFunc(s State, x interface{}) error { 61 | return x.(io.Closer).Close() 62 | } 63 | 64 | func closeWithErrorFunc(s State, x interface{}) error { 65 | c := x.(closerWithError) 66 | if err := s.Err(); err != nil { 67 | return c.CloseWithError(err) 68 | } 69 | return c.Close() 70 | } 71 | 72 | func unlockFunc(s State, x interface{}) error { 73 | x.(sync.Locker).Unlock() 74 | return nil 75 | } 76 | 77 | func voidFunc(s State, x interface{}) error { 78 | x.(func())() 79 | return nil 80 | } 81 | 82 | func voidErrorFunc(s State, x interface{}) error { 83 | return x.(func() error)() 84 | } 85 | 86 | func errorFunc(s State, x interface{}) error { 87 | x.(func(error))(s.Err()) 88 | return nil 89 | } 90 | 91 | func errorErrorFunc(s State, x interface{}) error { 92 | return x.(func(error) error)(s.Err()) 93 | } 94 | 95 | func stateErrorFunc(s State, x interface{}) error { 96 | return x.(func(s State) error)(s) 97 | } 98 | 99 | // Defer defers a call to x, which may be a function of the form: 100 | // - func() 101 | // - func() error 102 | // - func(error) 103 | // - func(error) error 104 | // - func(State) error 105 | // An error returned by any of these functions is passed to the error handlers. 106 | // 107 | // Performance-sensitive applications should use DeferFunc. 108 | func (e *Catcher) Defer(x interface{}, h ...Handler) { 109 | if x != nil { 110 | for i := len(h) - 1; i >= 0; i-- { 111 | e.deferred = append(e.deferred, deferData{h[i], nil}) 112 | } 113 | var f deferFunc 114 | switch x.(type) { 115 | case func(): 116 | f = voidFunc 117 | case func() error: 118 | f = voidErrorFunc 119 | case func(error): 120 | f = errorFunc 121 | case func(error) error: 122 | f = errorErrorFunc 123 | case func(s State) error: 124 | f = stateErrorFunc 125 | default: 126 | panic(fmt.Errorf(notSupported, x)) 127 | } 128 | e.deferred = append(e.deferred, deferData{x, f}) 129 | } 130 | } 131 | 132 | const notSupported = "errd: type %T not supported by Defer" 133 | 134 | // TODO 135 | // 136 | // // DeferScope calls f and calls all defers that were added within that call 137 | // // after it completes. An error that occurs in f is handled as if the error 138 | // // occurred in the caller. This includes errors in defer. DeferScope is used to 139 | // // force early cleanup of defers within a tight loop. 140 | // func (e *E) DeferScope(f func()) { 141 | // localDefer := len(e.deferred) 142 | // f() 143 | // doDefers(e, localDefer) 144 | 145 | // // TODO: bail if we detect an error. 146 | // } 147 | -------------------------------------------------------------------------------- /defer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package errc 6 | 7 | import ( 8 | "errors" 9 | "testing" 10 | ) 11 | 12 | type closer struct{ v *string } 13 | 14 | func (c *closer) Close() error { 15 | *c.v = "Close" 16 | return nil 17 | } 18 | 19 | type closerError struct{ v *string } 20 | 21 | func (c *closerError) Close() error { 22 | *c.v = "CloseNil" 23 | return nil 24 | } 25 | 26 | func (c *closerError) CloseWithError(err error) error { 27 | if err == nil { 28 | *c.v = "CloseNil" 29 | } else { 30 | *c.v = "Close:" + err.Error() 31 | } 32 | return errors.New("defer error") 33 | } 34 | 35 | type locker struct{ v *string } 36 | 37 | func (u *locker) Unlock() { *u.v = "Unlocked" } 38 | func (u *locker) Lock() {} 39 | 40 | type errInOnly struct{ v *string } 41 | 42 | func (e *errInOnly) Abort(err error) { *e.v = "Abort" } 43 | 44 | type wrapError struct { 45 | error 46 | } 47 | 48 | func TestDefer(t *testing.T) { 49 | var result string 50 | 51 | h1 := HandlerFunc(func(s State, err error) error { 52 | result += ":DefErr1" 53 | return err 54 | }) 55 | h2 := HandlerFunc(func(s State, err error) error { 56 | result += ":DefErr2" 57 | return err 58 | }) 59 | h3 := HandlerFunc(func(s State, err error) error { 60 | result += ":DefErr3" 61 | return err 62 | }) 63 | 64 | errTest := errors.New("Error") 65 | errWrap := wrapError{errTest} 66 | closer := &closer{&result} 67 | closerError := &closerError{&result} 68 | locker := &locker{&result} 69 | errInOnly := &errInOnly{&result} 70 | testCases := []struct { 71 | f func(e *Catcher) 72 | err error // body error 73 | wrapped error 74 | want string 75 | defHandlers []Handler 76 | }{{ 77 | f: func(e *Catcher) { e.Defer(closer.Close, h1) }, 78 | want: "Close", 79 | }, { 80 | f: func(e *Catcher) { 81 | e.deferFunc(closerError, closeWithErrorFunc, h1) 82 | }, 83 | want: "CloseNil", 84 | }, { 85 | f: func(e *Catcher) { 86 | e.Defer(closerError.CloseWithError, h1, h2, h3) 87 | }, 88 | err: errTest, 89 | wrapped: errWrap, 90 | want: "Close:Error:DefErr1:DefErr2:DefErr3", 91 | }, { 92 | f: func(e *Catcher) { e.Defer(locker.Unlock) }, 93 | err: errTest, 94 | wrapped: errWrap, 95 | want: "Unlocked", 96 | }, { 97 | f: func(e *Catcher) { 98 | e.Defer(errInOnly.Abort) 99 | }, 100 | err: errTest, 101 | wrapped: errWrap, 102 | want: "Abort", 103 | }, { 104 | f: func(e *Catcher) { 105 | e.Defer(func(s State) error { 106 | result += "State" 107 | return s.Err() 108 | }) 109 | }, 110 | err: errTest, 111 | wrapped: errWrap, 112 | want: "State", 113 | }, { 114 | f: func(e *Catcher) { 115 | e.Defer(func(s State) error { 116 | return errors.New("to discard") 117 | }, Discard) 118 | }, 119 | }, { 120 | f: func(e *Catcher) { 121 | e.Defer(func(s State) error { 122 | return errors.New("to discard") 123 | }) 124 | }, 125 | defHandlers: []Handler{Discard}, 126 | }, { 127 | f: func(e *Catcher) { 128 | e.deferFunc(closerError, closeWithError, h1) 129 | }, 130 | err: errTest, 131 | wrapped: errWrap, 132 | want: "Close:Error:DefErr1", 133 | }, { 134 | f: func(e *Catcher) { 135 | e.deferFunc(locker, unlock, h1) 136 | }, 137 | err: errTest, 138 | wrapped: errWrap, 139 | want: "Unlocked", 140 | }} 141 | for _, tc := range testCases { 142 | result = "" 143 | t.Run(tc.want, func(t *testing.T) { 144 | err := func() (err error) { 145 | ec := Catch(&err, tc.defHandlers...) 146 | defer ec.Handle() 147 | tc.f(&ec) 148 | ec.Must(tc.err, HandlerFunc(func(s State, err error) error { 149 | return wrapError{err} 150 | })) 151 | return nil 152 | }() 153 | if err != tc.wrapped { 154 | t.Errorf("err: got %q; want %q", err, tc.wrapped) 155 | } 156 | if result != tc.want { 157 | t.Errorf("result: got %q; want %q", result, tc.want) 158 | } 159 | }) 160 | } 161 | } 162 | 163 | func BenchmarkDeferFunc(b *testing.B) { 164 | x := &closer{} 165 | var err error 166 | e := Catch(&err) 167 | defer e.Handle() 168 | for i := 0; i < b.N; i++ { 169 | e.deferFunc(x, closeFunc) 170 | e.deferred = e.deferred[:0] 171 | } 172 | } 173 | 174 | func BenchmarkDeferClosure(b *testing.B) { 175 | x := &closer{} 176 | var err error 177 | e := Catch(&err) 178 | defer e.Handle() 179 | for i := 0; i < b.N; i++ { 180 | e.Defer(x.Close) 181 | e.deferred = e.deferred[:0] 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package errc simplifies error and defer handling. 6 | // 7 | // Overview 8 | // 9 | // Package errc is a burner package: a proof-of-concept to explore better 10 | // semantics for error and defer handling. Error handling and deferring using 11 | // this package looks like: 12 | // 13 | // func foo() (err error) { 14 | // e := errc.Catch(&err) 15 | // defer e.Handle() 16 | // 17 | // r, err := getReader() 18 | // e.Must(err, msg("read failed")) 19 | // e.Defer(r.Close) 20 | // 21 | // Checking for a nil error is replaced by a call to Must on an error catcher. 22 | // A defer statement is similarly replaced by a call to Defer. 23 | // 24 | // 25 | // The Problem 26 | // 27 | // Error handling in Go can also be tricky to get right. For instance, how to 28 | // use defer may depend on the situation. For a Close method that frees up 29 | // resources a simple use of defer suffices. For a CloseWithError method where a 30 | // nil error indicates the successful completion of a transaction, however, it 31 | // should be ensured that a nil error is not passed inadvertently, for instance 32 | // when there is a panic if a server runs out of memory and is killed by a 33 | // cluster manager. 34 | // 35 | // For instance, a correct way to commit a file to Google Cloud Storage is: 36 | // 37 | // func writeToGS(ctx context.Context, bucket, dst string, r io.Reader) (err error) { 38 | // client, err := storage.NewClient(ctx) 39 | // if err != nil { 40 | // return err 41 | // } 42 | // defer client.Close() 43 | // 44 | // w := client.Bucket(bucket).Object(dst).NewWriter(ctx) 45 | // err = errPanicking 46 | // defer func() { 47 | // if err != nil { 48 | // _ = w.CloseWithError(err) 49 | // } else { 50 | // err = w.Close() 51 | // } 52 | // } 53 | // _, err = io.Copy(w, r) 54 | // return err 55 | // } 56 | // 57 | // The err variable is initialized to errPanicking to ensure a non-nil err is 58 | // passed to CloseWithError when a panic occurs. This ensures that a panic 59 | // will not cause a corrupted file. If all went well, a separate path used 60 | // to collect the error returned by Close. Returning the error from Close is 61 | // important to signal retry logic the file was not successfully written. 62 | // Once the Close of w is successful all further errors are irrelevant. 63 | // The error of the first Close is therefor willfully ignored. 64 | // 65 | // These are a lot of subtleties to get the error handling working properly! 66 | // 67 | // The same can be achieved using errc as follows: 68 | // 69 | // func writeToGS(ctx context.Context, bucket, dst, src string) (err error) { 70 | // e := errc.Catch(&err) 71 | // defer e.Handle() 72 | // 73 | // client, err := storage.NewClient(ctx) 74 | // e.Must(err) 75 | // e.Defer(client.Close, errd.Discard) 76 | // 77 | // w := client.Bucket(bucket).Object(dst).NewWriter(ctx) 78 | // e.Defer(w.CloseWithError) 79 | // 80 | // _, err = io.Copy(w, r) 81 | // return err 82 | // } 83 | // 84 | // Observe how a straightforward application of idiomatic check-and-defer 85 | // pattern leads to the correct results. The error of the first Close is now 86 | // ignored explicitly using the Discard error handler, 87 | // making it clear that this is what the programmer intended. 88 | // 89 | // 90 | // Error Handlers 91 | // 92 | // Error handlers can be used to decorate errors, log them, or do anything else 93 | // you usually do with errors. 94 | // 95 | // Suppose we want to use github.com/pkg/errors to decorate errors. A simple 96 | // handler can be defined as: 97 | // 98 | // type msg string 99 | // 100 | // func (m msg) Handle(s errc.State, err error) error { 101 | // return errors.WithMessage(err, string(m)) 102 | // } 103 | // 104 | // This handler can then be used as follows: 105 | // 106 | // func writeToGS(ctx context.Context, bucket, dst, src string) (err error) { 107 | // e := errc.Catch(&err) 108 | // defer e.Handle() 109 | // 110 | // client, err := storage.NewClient(ctx) 111 | // e.Must(err, msg("error opening client")) 112 | // e.Defer(client.Close) 113 | // 114 | // w := client.Bucket(bucket).Object(dst).NewWriter(ctx) 115 | // e.Defer(w.CloseWithError) 116 | // 117 | // _, err = io.Copy(w, r) 118 | // e.Must(err, msg("error copying contents")) 119 | // return nil 120 | // } 121 | // 122 | package errc 123 | -------------------------------------------------------------------------------- /errc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package errc 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | ) 11 | 12 | // Catch returns an error Catcher, which is used to funnel errors from panics 13 | // and failed calls to Try. It must be passed the location of an error variable. 14 | // The user must defer a call to Handle immediately after the creation of the 15 | // Catcher as follows: 16 | // e := errc.Catch(&err) 17 | // defer e.Handle() 18 | func Catch(err *error, h ...Handler) Catcher { 19 | ec := Catcher{core{defaultHandlers: h, err: err}} 20 | ec.deferred = ec.buf[:0] 21 | return ec 22 | } 23 | 24 | const bufSize = 3 25 | 26 | type core struct { 27 | defaultHandlers []Handler 28 | deferred []deferData 29 | buf [bufSize]deferData 30 | err *error 31 | inPanic bool 32 | } 33 | 34 | // A Catcher coordinates error and defer handling. 35 | type Catcher struct{ core } 36 | 37 | var errHandlerFirst = errors.New("errd: handler may not be first argument") 38 | 39 | // Must causes a return from a function if err is not nil, and after the error 40 | // is not nullified by any of the Handlers. 41 | func (e *Catcher) Must(err error, h ...Handler) { 42 | if err != nil { 43 | processError(e, err, h) 44 | } 45 | } 46 | 47 | // State represents the error state passed to custom error handlers. 48 | type State interface { 49 | // Panicking reports whether the error resulted from a panic. If true, 50 | // the panic will be resume after error handling completes. An error handler 51 | // cannot rewrite an error when panicing. 52 | Panicking() bool 53 | 54 | // Err reports the first error that passed through an error handler chain. 55 | // Note that this is always a different error (or nil) than the one passed 56 | // to an error handler. 57 | Err() error 58 | } 59 | 60 | type state struct{ core } 61 | 62 | func (s *state) Panicking() bool { return s.inPanic } 63 | 64 | func (s *state) Err() error { 65 | if s.err == nil { 66 | return nil 67 | } 68 | return *s.err 69 | } 70 | 71 | var errOurPanic = errors.New("errd: our panic") 72 | 73 | // Handle manages the error handling and defer processing. It must be called 74 | // after any call to Catch. 75 | func (e *Catcher) Handle() { 76 | switch r := recover(); r { 77 | case nil: 78 | finishDefer(e) 79 | case errOurPanic: 80 | finishDefer(e) 81 | default: 82 | e.inPanic = true 83 | err2, ok := r.(error) 84 | if !ok { 85 | err2 = fmt.Errorf("errd: paniced: %v", r) 86 | } 87 | *e.err = err2 88 | finishDefer(e) 89 | // Check whether there are still defers left to do and then 90 | // recursively defer. 91 | panic(r) 92 | } 93 | } 94 | 95 | func doDefers(e *Catcher, barrier int) { 96 | for len(e.deferred) > barrier { 97 | i := len(e.deferred) - 1 98 | d := e.deferred[i] 99 | e.deferred = e.deferred[:i] 100 | if d.f == nil { 101 | continue 102 | } 103 | if err := d.f((*state)(e), d.x); err != nil { 104 | processDeferError(e, err) 105 | } 106 | } 107 | } 108 | 109 | // finishDefer processes remaining defers after we already have a panic. 110 | // We therefore ignore any panic caught here, knowing that we will panic on an 111 | // older panic after returning. 112 | func finishDefer(e *Catcher) { 113 | if len(e.deferred) > 0 { 114 | defer e.Handle() 115 | doDefers(e, 0) 116 | } 117 | } 118 | 119 | type errorHandler struct { 120 | e *Catcher 121 | err *error 122 | } 123 | 124 | func (h errorHandler) handle(eh Handler) (done bool) { 125 | newErr := eh.Handle((*state)(h.e), *h.err) 126 | if newErr == nil { 127 | return true 128 | } 129 | *h.err = newErr 130 | return false 131 | 132 | } 133 | 134 | func processDeferError(e *Catcher, err error) { 135 | eh := errorHandler{e: e, err: &err} 136 | hadHandler := false 137 | // Apply handlers added by Defer methods. A zero deferred value signals that 138 | // we have custom defer handler for the subsequent fields. 139 | for i := len(e.deferred); i > 0 && e.deferred[i-1].f == nil; i-- { 140 | hadHandler = true 141 | if eh.handle(e.deferred[i-1].x.(Handler)) { 142 | return 143 | } 144 | } 145 | if !hadHandler { 146 | for _, h := range e.defaultHandlers { 147 | if eh.handle(h) { 148 | return 149 | } 150 | } 151 | } 152 | if *e.err == nil { 153 | *e.err = err 154 | } 155 | } 156 | 157 | func processError(e *Catcher, err error, handlers []Handler) { 158 | eh := errorHandler{e: e, err: &err} 159 | for _, h := range handlers { 160 | if eh.handle(h) { 161 | return 162 | } 163 | } 164 | if len(handlers) == 0 { 165 | for _, h := range e.defaultHandlers { 166 | if eh.handle(h) { 167 | return 168 | } 169 | } 170 | } 171 | if *e.err == nil { 172 | *e.err = err 173 | } 174 | bail(e) 175 | } 176 | 177 | func bail(e *Catcher) { 178 | // Do defers now and save an extra defer. 179 | doDefers(e, 0) 180 | panic(errOurPanic) 181 | } 182 | -------------------------------------------------------------------------------- /errc_test.go: -------------------------------------------------------------------------------- 1 | package errc 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | const ( 13 | success = iota 14 | deferError 15 | deferPanic 16 | retError 17 | bodyPanic 18 | numAction 19 | ) 20 | 21 | func actionStr(action int) string { 22 | switch action { 23 | case success: 24 | return "success" 25 | case deferError: 26 | return "closeErr" 27 | case retError: 28 | return "retErr" 29 | case bodyPanic: 30 | return "panic" 31 | case deferPanic: 32 | return "panicInDefer" 33 | } 34 | panic("ex: unreachable") 35 | } 36 | 37 | var ( 38 | testCases = [][]int{ 39 | {success}, 40 | {deferError}, 41 | {deferPanic}, 42 | {retError}, 43 | {bodyPanic}, 44 | } 45 | 46 | retErrors, deferErrors, panicErrors, deferPanicErrors []error 47 | ) 48 | 49 | func init() { 50 | // Add new test cases, each time adding one of two digits to each existing 51 | // one. 52 | for i := 0; i < 3; i++ { 53 | prev := testCases 54 | for j := success; j < retError; j++ { 55 | for _, tc := range prev { 56 | testCases = append(testCases, append([]int{j}, tc...)) 57 | } 58 | } 59 | } 60 | 61 | // pre-allocate sufficient return and close errors 62 | for id := 0; id < 10; id++ { 63 | retErrors = append(retErrors, fmt.Errorf("return-err:%d", id)) 64 | deferErrors = append(deferErrors, fmt.Errorf("close-err:%d", id)) 65 | panicErrors = append(panicErrors, fmt.Errorf("panic-err:%d", id)) 66 | deferPanicErrors = append(deferPanicErrors, fmt.Errorf("defer-panic-err:%d", id)) 67 | } 68 | } 69 | 70 | type idCloser struct { 71 | id int 72 | action int 73 | w io.Writer 74 | } 75 | 76 | func (c *idCloser) Close() error { 77 | if c.w != nil { 78 | fmt.Fprintln(c.w, "closed", c.id) 79 | } 80 | switch c.action { 81 | case deferError: 82 | return deferErrors[c.id] 83 | case deferPanic: 84 | panic(deferPanicErrors[c.id]) 85 | } 86 | return nil 87 | } 88 | 89 | func (c *idCloser) CloseWithError(err error) error { 90 | if c.w != nil { 91 | if err == nil { 92 | fmt.Fprintln(c.w, "closed", c.id) 93 | } else { 94 | fmt.Fprint(c.w, "closed with error: ", c.id) 95 | fmt.Fprintln(c.w, ":", err) 96 | } 97 | } 98 | switch c.action { 99 | case deferError: 100 | return deferErrors[c.id] 101 | case deferPanic: 102 | panic(deferPanicErrors[c.id]) 103 | } 104 | return nil 105 | } 106 | 107 | type errFunc func() error 108 | 109 | // TestConformance simulates 1 or more blocks of creating values and handling 110 | // errors and defers and compares the result of using the traditional style Go 111 | // and using package errd. 112 | func TestConformanceDefer(t *testing.T) { 113 | for _, tc := range testCases { 114 | t.Run(key(tc), func(t *testing.T) { 115 | want := simulate(tc, properTraditionalDefer) 116 | got := simulate(tc, errdClosureDefer) 117 | if got != want { 118 | t.Errorf("\n=== got:\n%s=== want:\n%s", got, want) 119 | } 120 | }) 121 | } 122 | } 123 | 124 | // idiomaticTraditionalDefer is how error handling is usually done manually for 125 | // the case described in TestConformance. However, this approach may miss 126 | // detecting and returning errors and is not correct in the general case. 127 | // We include this set for illustrative purposes, including benchmarks, where 128 | // it can be used to show the cost of doing things more properly. 129 | func idiomaticTraditionalDefer(w io.Writer, actions []int) errFunc { 130 | closers := make([]idCloser, len(actions)) 131 | return func() error { 132 | for i, a := range actions { 133 | c, err := retDefer(w, closers, i, a) 134 | if err != nil { 135 | return err 136 | } 137 | defer c.Close() 138 | } 139 | return nil 140 | } 141 | } 142 | 143 | // properTraditionalDefer goes beyond the common ways in which errors are 144 | // handled and also detects errors resulting from close. 145 | func properTraditionalDefer(w io.Writer, actions []int) errFunc { 146 | closers := make([]idCloser, len(actions)) 147 | return func() (errOut error) { 148 | for i, a := range actions { 149 | c, err := retDefer(w, closers, i, a) 150 | if err != nil { 151 | return err 152 | } 153 | defer func() { 154 | if err := c.Close(); err != nil && errOut == nil { 155 | errOut = err 156 | } 157 | }() 158 | } 159 | return nil 160 | } 161 | } 162 | 163 | func errdClosureDefer(w io.Writer, actions []int) errFunc { 164 | closers := make([]idCloser, len(actions)) 165 | return func() (err error) { 166 | e := Catch(&err) 167 | defer e.Handle() 168 | for i, a := range actions { 169 | c, err := retDefer(w, closers, i, a) 170 | e.Must(err) 171 | e.Defer(c.Close) 172 | } 173 | return err 174 | } 175 | } 176 | 177 | func errdFuncDefer(w io.Writer, actions []int) errFunc { 178 | closers := make([]idCloser, len(actions)) 179 | return func() (err error) { 180 | e := Catch(&err) 181 | defer e.Handle() 182 | for i, a := range actions { 183 | c, err := retDefer(w, closers, i, a) 184 | e.Must(err) 185 | e.deferFunc(c, close) 186 | } 187 | return err 188 | } 189 | } 190 | 191 | // TestConformanceWithError simulates 1 or more blocks of creating values and 192 | // handling errors and defers, where the deferred function needs to be passed 193 | // *any* earlier occurring error, including those from panics and those 194 | // originating in other defer blocks. 195 | func TestConformanceDeferWithError(t *testing.T) { 196 | for _, tc := range testCases { 197 | t.Run(key(tc), func(t *testing.T) { 198 | want := simulate(tc, pedanticTraditionalDeferWithError) 199 | got := simulate(tc, errdClosureDeferWithError) 200 | if got != want { 201 | t.Errorf("\n=== got:\n%s=== want:\n%s", got, want) 202 | } 203 | }) 204 | } 205 | } 206 | 207 | // pedanticTraditionalDeferWithError implements a way to catch ALL errors 208 | // preceding a call to CloseWithError, including panics in the body and 209 | // other defers, without using the errd package. 210 | func pedanticTraditionalDeferWithError(w io.Writer, actions []int) errFunc { 211 | closers := make([]idCloser, len(actions)) 212 | return func() (errOut error) { 213 | var isPanic bool 214 | defer func() { 215 | // We may still have a panic after our last call to defer so catch. 216 | if r := recover(); r != nil { 217 | panic(r) 218 | } 219 | // Panic again for panics we caught earlier to pass to defers. 220 | if isPanic { 221 | panic(errOut) 222 | } 223 | }() 224 | for i, a := range actions { 225 | c, err := retDeferWithErr(w, closers, i, a) 226 | if err != nil { 227 | return err 228 | } 229 | defer func() { 230 | // We need to recover any possible panic to not miss out on 231 | // passing the panic. Panics override any previous error. 232 | if r := recover(); r != nil { 233 | switch v := r.(type) { 234 | case error: 235 | errOut = v 236 | default: 237 | errOut = fmt.Errorf("%v", v) 238 | } 239 | isPanic = true 240 | } 241 | if errOut != nil { 242 | c.CloseWithError(errOut) 243 | } else { 244 | if err := c.Close(); err != nil && errOut == nil { 245 | errOut = err 246 | } 247 | } 248 | }() 249 | } 250 | return nil 251 | } 252 | } 253 | 254 | func errdClosureDeferWithError(w io.Writer, actions []int) errFunc { 255 | closers := make([]idCloser, len(actions)) 256 | return func() (err error) { 257 | e := Catch(&err) 258 | defer e.Handle() 259 | for i, a := range actions { 260 | c, err := retDeferWithErr(w, closers, i, a) 261 | e.Must(err, identity) 262 | e.Defer(c.CloseWithError) 263 | } 264 | return nil 265 | } 266 | } 267 | 268 | func errdFuncDeferWithError(w io.Writer, actions []int) errFunc { 269 | closers := make([]idCloser, len(actions)) 270 | return func() (err error) { 271 | e := Catch(&err) 272 | defer e.Handle() 273 | for i, a := range actions { 274 | c, err := retDeferWithErr(w, closers, i, a) 275 | e.Must(err, identity) 276 | e.deferFunc(c, closeWithErrorFunc) 277 | } 278 | return nil 279 | } 280 | } 281 | 282 | type benchCase struct { 283 | name string 284 | f func(w io.Writer, actions []int) errFunc 285 | } 286 | 287 | var testFuncsDeferClose = []benchCase{ 288 | {"idiomatic traditional", idiomaticTraditionalDefer}, 289 | {"proper traditional", properTraditionalDefer}, 290 | {"errd/closer", errdClosureDefer}, 291 | } 292 | 293 | var testFuncsDeferCloseWithError = []benchCase{ 294 | {"traditional/closerwe", pedanticTraditionalDeferWithError}, 295 | {"errd/closerwe", errdClosureDeferWithError}, 296 | } 297 | 298 | var testFuncsNoDefer = []benchCase{ 299 | {"traditional", traditionalCheck}, 300 | {"errd", errdClosureCheck}, 301 | } 302 | 303 | func traditionalCheck(w io.Writer, actions []int) errFunc { 304 | return func() (err error) { 305 | for i, a := range actions { 306 | err := retNoDefer(w, i, a) 307 | if err != nil { 308 | return err 309 | } 310 | } 311 | return nil 312 | } 313 | } 314 | 315 | func errdClosureCheck(w io.Writer, actions []int) errFunc { 316 | return func() (err error) { 317 | e := Catch(&err) 318 | defer e.Handle() 319 | for i, a := range actions { 320 | e.Must(retNoDefer(w, i, a)) 321 | } 322 | return nil 323 | } 324 | } 325 | 326 | func retDefer(w io.Writer, closers []idCloser, id, action int) (io.Closer, error) { 327 | // pre-allocate io.Closers. This is not realistice, but eliminates this 328 | // allocation from the measurements. 329 | closers[id] = idCloser{id, action, w} 330 | switch action { 331 | case success, deferError, deferPanic: 332 | return &closers[id], nil 333 | case retError: 334 | return nil, retErrors[id] 335 | case bodyPanic: 336 | panic(panicErrors[id]) 337 | } 338 | panic("errd: unreachable") 339 | } 340 | 341 | func retDeferWithErr(w io.Writer, closers []idCloser, id, action int) (closerWithError, error) { 342 | // pre-allocate io.Closers. This is not realistice, but eliminates this 343 | // allocation from the measurements. 344 | closers[id] = idCloser{id, action, w} 345 | switch action { 346 | case success, deferError, deferPanic: 347 | return &closers[id], nil 348 | case retError: 349 | return nil, retErrors[id] 350 | case bodyPanic: 351 | panic(panicErrors[id]) 352 | } 353 | panic("errd: unreachable") 354 | } 355 | 356 | func retNoDefer(w io.Writer, id, action int) error { 357 | // pre-allocate io.Closers. This is not realistice, but eliminates this 358 | // allocation from the measurements. 359 | switch action { 360 | case success: 361 | return nil 362 | case retError: 363 | return retErrors[id] 364 | case bodyPanic: 365 | panic(panicErrors[id]) 366 | } 367 | panic("errd: unreachable") 368 | } 369 | 370 | func key(test []int) (key string) { 371 | s := []string{} 372 | for _, a := range test { 373 | s = append(s, actionStr(a)) 374 | } 375 | return strings.Join(s, "-") 376 | } 377 | 378 | func simulate(actions []int, f func(w io.Writer, actions []int) errFunc) (result string) { 379 | w := &bytes.Buffer{} 380 | defer func() { 381 | if err := recover(); err != nil { 382 | fmt.Fprintf(w, "P:%v\n", err) 383 | } 384 | result = w.String() 385 | }() 386 | fmt.Fprintln(w, f(w, actions)()) 387 | return "" // set in defer 388 | } 389 | 390 | var benchCases = []struct { 391 | allocs int // number of extra allocs 392 | actions []int 393 | }{ 394 | {0, []int{success}}, 395 | {0, []int{success, success}}, 396 | {0, []int{success, success, success}}, 397 | {1, []int{success, success, success, success}}, 398 | // Uncomment these for more benchmarks. 399 | // {1, []int{success, success, success, success, success}}, 400 | // {0, []int{retError}}, 401 | // {0, []int{success, retError}}, 402 | // {0, []int{success, success, retError}}, 403 | // {0, []int{success, success, success, retError}}, 404 | // {1, []int{success, success, success, success, retError}}, 405 | } 406 | 407 | func runBenchCases(b *testing.B, bf []benchCase) { 408 | for _, bc := range benchCases { 409 | for _, bf := range bf { 410 | b.Run(key(bc.actions)+"/"+bf.name, func(b *testing.B) { 411 | f := bf.f(nil, bc.actions) 412 | for i := 0; i < b.N; i++ { 413 | f() 414 | } 415 | }) 416 | } 417 | } 418 | } 419 | 420 | func BenchmarkNoDefer(b *testing.B) { 421 | runBenchCases(b, testFuncsNoDefer) 422 | } 423 | 424 | func BenchmarkDeferClose(b *testing.B) { 425 | runBenchCases(b, testFuncsDeferClose) 426 | } 427 | 428 | func BenchmarkDeferCloseWithError(b *testing.B) { 429 | runBenchCases(b, testFuncsDeferCloseWithError) 430 | } 431 | func TestPanic(t *testing.T) { 432 | errFoo := errors.New("foo") 433 | testCases := []struct { 434 | f func(e *Catcher) 435 | p interface{} 436 | err string 437 | noPanic bool 438 | }{{ 439 | f: func(e *Catcher) { 440 | }, 441 | p: nil, 442 | noPanic: true, 443 | }, { 444 | f: func(e *Catcher) { 445 | panic("bar") 446 | }, 447 | p: "bar", 448 | err: "errd: paniced: bar", 449 | }, { 450 | f: func(e *Catcher) { 451 | panic(errFoo) 452 | }, 453 | p: errFoo, 454 | err: "foo", 455 | }, { 456 | f: func(e *Catcher) { 457 | panic(2) 458 | }, 459 | p: 2, 460 | err: "errd: paniced: 2", 461 | }, { 462 | f: func(e *Catcher) { 463 | e.deferFunc(nil, nil) // panic: nil func 464 | }, 465 | p: errNilFunc, 466 | err: errNilFunc.Error(), 467 | }, { 468 | f: func(e *Catcher) { 469 | e.Defer(1) // panic: not supported 470 | }, 471 | err: "errd: type int not supported by Defer", 472 | }} 473 | for _, tc := range testCases { 474 | paniced := false 475 | panicHandler := HandlerFunc(func(s State, err error) error { 476 | paniced = s.Panicking() 477 | return err 478 | }) 479 | t.Run("", func(t *testing.T) { 480 | defer func() { 481 | r := recover() 482 | if tc.p != nil && (r == nil || r != nil && r != tc.p) { 483 | t.Errorf("got %v; want %v", r, tc.p) 484 | } 485 | if paniced != !tc.noPanic { 486 | t.Errorf("got %v; want %v", paniced, !tc.noPanic) 487 | } 488 | }() 489 | var err error 490 | e := Catch(&err, panicHandler) 491 | defer e.Handle() 492 | e.deferFunc(nil, func(s State, x interface{}) error { 493 | err := s.Err() 494 | if err == nil && tc.err != "" || err != nil && err.Error() != tc.err { 495 | t.Errorf("got %q; want %q", err, tc.err) 496 | } 497 | return err 498 | }) 499 | tc.f(&e) 500 | }) 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package errc_test 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "io" 11 | "io/ioutil" 12 | "net/http" 13 | "os" 14 | "strings" 15 | "time" 16 | 17 | "github.com/mpvl/errc" 18 | ) 19 | 20 | func ExampleHandler_fatal() { 21 | func() (err error) { 22 | e := errc.Catch(&err, errc.Fatal) 23 | defer e.Handle() 24 | 25 | r, err := newReader() 26 | e.Must(err) 27 | e.Defer(r.Close) 28 | 29 | r, err = newFaultyReader() 30 | e.Must(err) 31 | e.Defer(r.Close) 32 | return nil 33 | }() 34 | } 35 | 36 | func newReader() (io.ReadCloser, error) { 37 | return ioutil.NopCloser(strings.NewReader("Hello World!")), nil 38 | } 39 | 40 | func newFaultyReader() (io.ReadCloser, error) { 41 | return nil, errors.New("errd_test: error") 42 | } 43 | 44 | func ExampleCatch() { 45 | func() (err error) { 46 | e := errc.Catch(&err) 47 | defer e.Handle() 48 | 49 | r, err := newReader() // contents: Hello World! 50 | e.Must(err) 51 | e.Defer(r.Close) 52 | 53 | _, err = io.Copy(os.Stdout, r) 54 | e.Must(err) 55 | return nil 56 | }() 57 | // Output: 58 | // Hello World! 59 | } 60 | 61 | func ExampleCatch_pipe() { 62 | r, w := io.Pipe() 63 | go func() { 64 | var err error 65 | e := errc.Catch(&err) 66 | defer e.Handle() 67 | 68 | e.Defer(w.CloseWithError) 69 | 70 | r, err := newReader() // contents: Hello World! 71 | e.Must(err) 72 | e.Defer(r.Close) 73 | 74 | _, err = io.Copy(w, r) 75 | }() 76 | io.Copy(os.Stdout, r) 77 | 78 | // The above goroutine is equivalent to: 79 | // 80 | // go func() { 81 | // // err is used to intercept downstream errors. Note that we set it to a 82 | // // sentinel even though we recover the panic below to cover the case of 83 | // // a panic between the two defers. This is very unlikely to be 84 | // // necessary, but remember: a panic may be caused by external factors 85 | // // and code requiring high reliability should always consider the 86 | // // possibility of a panic occurring at any point. 87 | // var err = errors.New("panicking") 88 | // 89 | // // No need to intercept error: io.PipeWriter.CloseWithError always 90 | // // returns nil. 91 | // defer w.CloseWithError(err) 92 | // 93 | // // Ensure that CloseWithError is not called with a nil error on panic. 94 | // // In this case use recover: because we set err multiple times, it 95 | // // seems a bit easier than managing everything by sentinel. 96 | // defer func() { 97 | // if v := recover(); v != nil { 98 | // err = errors.New("panicking") 99 | // } 100 | // }() 101 | // 102 | // r, err := newReader() 103 | // if err != nil { 104 | // return 105 | // } 106 | // defer func() { 107 | // if errC := r.Close(); err == nil { 108 | // err = errC 109 | // } 110 | // }() 111 | // 112 | // _, err = io.Copy(w, r) 113 | // }() 114 | 115 | // Output: 116 | // Hello World! 117 | } 118 | 119 | func do(ctx context.Context) { 120 | // do something 121 | } 122 | 123 | // ExampleCatcher_Defer_cancelHelper shows how a helper function may call a 124 | // defer in the caller's E. Notice how contextWithTimeout taking care of the 125 | // call to Defer is both evil and handy at the same time. Such a thing would 126 | // likely not be allowed if this were a language feature. 127 | func ExampleCatcher_Defer_cancelHelper() { 128 | contextWithTimeout := func(e *errc.Catcher, req *http.Request) context.Context { 129 | var cancel context.CancelFunc 130 | ctx := req.Context() 131 | timeout, err := time.ParseDuration(req.FormValue("timeout")) 132 | if err == nil { 133 | // The request has a timeout, so create a context that is 134 | // canceled automatically when the timeout expires. 135 | ctx, cancel = context.WithTimeout(ctx, timeout) 136 | } else { 137 | ctx, cancel = context.WithCancel(ctx) 138 | } 139 | e.Defer(cancel) 140 | return ctx 141 | } 142 | 143 | http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 144 | var err error 145 | e := errc.Catch(&err) 146 | defer e.Handle() 147 | 148 | ctx := contextWithTimeout(&e, req) 149 | do(ctx) 150 | }) 151 | } 152 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package errc 6 | 7 | import ( 8 | "os" 9 | ) 10 | 11 | // A Handler processes errors. 12 | type Handler interface { 13 | // Handle processes an error detected by Must or Defer. It may replace the 14 | // error with another one, return it as is, or return nil, in which case 15 | // error handling is terminated and Must or Defer will continue operation 16 | // as if the error did not occur. 17 | Handle(s State, err error) error 18 | } 19 | 20 | var ( 21 | // Discard is a handler that discards the given error, causing 22 | // normal control flow to resume. 23 | Discard Handler = HandlerFunc(discard) 24 | 25 | // Fatal is handler that causes execution to halt. 26 | Fatal Handler = HandlerFunc(fatal) 27 | ) 28 | 29 | func discard(s State, err error) error { return nil } 30 | 31 | func fatal(s State, err error) error { 32 | os.Exit(1) 33 | return nil 34 | } 35 | 36 | // The HandlerFunc type is an adapter to allow the use of ordinary functions as 37 | // error handlers. If f is a function with the appropriate signature, 38 | // HandlerFunc(f) is a Handler that calls f. 39 | type HandlerFunc func(s State, err error) error 40 | 41 | // Handle calls f(s, err). 42 | func (f HandlerFunc) Handle(s State, err error) error { 43 | return f(s, err) 44 | } 45 | -------------------------------------------------------------------------------- /handler_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package errc 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | ) 11 | 12 | type intErr int 13 | 14 | func (i intErr) Error() string { return fmt.Sprint(int(i)) } 15 | 16 | var ( 17 | err0 = intErr(0) 18 | err1 = intErr(1) 19 | err2 = intErr(2) 20 | err3 = intErr(3) 21 | 22 | identity = HandlerFunc(func(s State, err error) error { 23 | return err 24 | }) 25 | 26 | inc = HandlerFunc(func(s State, err error) error { 27 | i := err.(intErr) 28 | return i + 1 29 | }) 30 | 31 | dec = HandlerFunc(func(s State, err error) error { 32 | i := err.(intErr) 33 | return i - 1 34 | }) 35 | ) 36 | 37 | func TestOptions(t *testing.T) { 38 | // Error unconditionally generated in the second statement. 39 | testCases := []struct { 40 | desc string 41 | defHandlers []Handler 42 | handlers1 []Handler 43 | handlersD1 []Handler 44 | handlers2 []Handler 45 | def1 interface{} 46 | err1 error 47 | err2 error 48 | want error 49 | }{{ 50 | desc: "no option", 51 | err1: nil, 52 | want: nil, 53 | }, { 54 | desc: "default option", 55 | defHandlers: []Handler{inc}, 56 | err1: err0, 57 | want: err1, 58 | }, { 59 | desc: "default twice", 60 | defHandlers: []Handler{inc, inc}, 61 | err1: err0, 62 | want: err2, 63 | }, { 64 | desc: "mask default", 65 | defHandlers: []Handler{inc}, 66 | handlers1: []Handler{dec}, 67 | err1: err2, 68 | want: err1, 69 | }, { 70 | desc: "test DefaultFunc", 71 | defHandlers: []Handler{inc}, 72 | handlers1: []Handler{dec}, 73 | err1: err2, 74 | want: err1, 75 | }, { 76 | desc: "handler once", 77 | handlers1: []Handler{inc}, 78 | err1: err1, 79 | want: err2, 80 | }, { 81 | desc: "handler twice", 82 | handlers1: []Handler{inc, inc}, 83 | err1: err1, 84 | want: err3, 85 | }, { 86 | desc: "erase", 87 | handlers1: []Handler{Discard}, 88 | handlers2: []Handler{Discard}, 89 | err1: err1, 90 | err2: err2, 91 | want: nil, 92 | }, { 93 | desc: "erase in default handler", 94 | defHandlers: []Handler{Discard}, 95 | err1: err1, 96 | err2: err2, 97 | want: nil, 98 | }, { 99 | desc: "handler cannot clear error", 100 | handlers2: []Handler{Discard}, 101 | err1: err1, 102 | err2: err2, 103 | want: err1, 104 | }} 105 | for _, tc := range testCases { 106 | t.Run(tc.desc, func(t *testing.T) { 107 | got := func() (err error) { 108 | e := Catch(&err, tc.defHandlers...) 109 | defer e.Handle() 110 | args := []interface{}{tc.def1, tc.err1} 111 | for _, h := range tc.handlers1 { 112 | args = append(args, h) 113 | } 114 | e.Must(tc.err1, tc.handlers1...) 115 | e.Defer(tc.def1, tc.handlersD1...) 116 | e.Must(tc.err2, tc.handlers2...) 117 | return nil 118 | }() 119 | if got != tc.want { 120 | t.Errorf("got %v; want %v", got, tc.want) 121 | } 122 | }) 123 | } 124 | } 125 | 126 | type msg string 127 | 128 | func (m msg) Handle(s State, err error) error { 129 | return err 130 | } 131 | 132 | func TestOptionAlloc(t *testing.T) { 133 | var e Catcher 134 | f := testing.AllocsPerRun(10, func() { 135 | // Technically an allocation, although it is a really cheap one as of 136 | // Go 1.9. 137 | e.Must(nil, msg("foo")) 138 | }) 139 | if f > 1 { 140 | t.Errorf("got %v; want %v", f, 0) 141 | } 142 | } 143 | 144 | func BenchmarkNoOption(b *testing.B) { 145 | var err error 146 | e := Catch(&err) 147 | defer e.Handle() 148 | for i := 0; i < b.N; i++ { 149 | e.Must(nil) 150 | } 151 | } 152 | 153 | func BenchmarkSavedHandlerOption(b *testing.B) { 154 | test := Handler(msg("test")) 155 | var err error 156 | e := Catch(&err) 157 | defer e.Handle() 158 | for i := 0; i < b.N; i++ { 159 | e.Must(nil, test) 160 | } 161 | 162 | } 163 | 164 | func BenchmarkStringOption(b *testing.B) { 165 | var err error 166 | e := Catch(&err) 167 | defer e.Handle() 168 | for i := 0; i < b.N; i++ { 169 | e.Must(nil, msg("error doing benchmark")) 170 | } 171 | 172 | } 173 | 174 | func BenchmarkFuncOption(b *testing.B) { 175 | var err error 176 | e := Catch(&err) 177 | defer e.Handle() 178 | for i := 0; i < b.N; i++ { 179 | e.Must(nil, HandlerFunc(func(s State, err error) error { 180 | return err 181 | })) 182 | } 183 | } 184 | --------------------------------------------------------------------------------