From a35465888aaeae469e835c073743d109e0af2256 Mon Sep 17 00:00:00 2001 From: Eric Myhre Date: Sun, 1 Mar 2015 03:43:02 -0600 Subject: [PATCH 1/6] Add 'try' system for scoped panic handling. This makes it possible to use panics for flow control without redundant error checking (or the chance to forget to check returned errors), and collaborates with the error type system to all quick and easy filtering of errors. --- try/try.go | 144 ++++++++++++++++++++++++++++++++++++++++ try/try_stack_test.go | 54 +++++++++++++++ try/try_test.go | 151 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 349 insertions(+) create mode 100644 try/try.go create mode 100644 try/try_stack_test.go create mode 100644 try/try_test.go diff --git a/try/try.go b/try/try.go new file mode 100644 index 0000000..7638a45 --- /dev/null +++ b/try/try.go @@ -0,0 +1,144 @@ +/* + `try` provides idioms for scoped panic handling. + + It's specifically aware of spacemonkey errors and can easily create + type-aware handling blocks. + + For a given block of code that is to be tried, several kinds of error + handling are possible: + - `Catch(type, func(err) {...your handler...})` + - `CatchAll(func(err) {...your handler...})` + - `Finally(func() {...your handler...})` + + `Catch` and `CatchAll` blocks consume the error -- it will not be re-raised + unless the handlers explicitly do so. `Finally` blocks run even in the + absense of errors (much like regular defers), and do not consume errors -- + they will be re-raised after the execution of the `Finally` block. + + Matching of errors occurs in order. This has a few implications: + - If using `Catch` blocks with errors that are subclasses of other errors + you're handling in the same sequence, put the most specific ones first. + - `CatchAll` blocks should be last (or they'll eat all errors, + even if you declare more `Catch` blocks later). + + `Finally` blocks will be run at the end of the error handling sequence + regardless of their declaration order. + + Additional panics from a `Catch` or `CatchAll` block will still cause + `Finally` blocks to be executed. However, note that additional panics + raised from any handler blocks will cause the original error to be masked + -- be careful of this. + + Panics with values that are not spacemonkey errors will be handled + (no special treatment; they'll hit `CatchAll` blocks and `Finally` blocks; + it would of course be silly for them to hit `Catch` blocks since those + use spacemonkey-errors types). Panics with values that are not of golang's + `error` type at all will trigger `Finally` blocks but otherwise + be immediately repanicked. + + A `try.Do(func() {...})` with no attached errors handlers is legal but + pointless. A `try.Do(func() {...})` with no `Done()` will never run the + function (which is good; you won't forget to call it). + + For spacemonkey errors, the 'exit' path will be automatically recorded for + each time the errors is rethrown. This is not a complete record of where + the error has been, and reexamining the current stack may give a more + complete picture. Note that 'finally' blocks will never be recorded + (unless of course they raise a new error!), since they are functions that + return normally. +*/ +package try + +import ( + "github.com/spacemonkeygo/errors" +) + +type plan struct { + main func() + catch []check + finally func() +} + +type check struct { + match *errors.ErrorClass + handler func(err *errors.Error) + anyhandler func(err error) +} + +func Do(f func()) *plan { + return &plan{main: f, finally: func() {}} +} + +func (p *plan) Catch(kind *errors.ErrorClass, handler func(err *errors.Error)) *plan { + p.catch = append(p.catch, check{ + match: kind, + handler: handler, + }) + return p +} + +func (p *plan) CatchAll(handler func(err error)) *plan { + p.catch = append(p.catch, check{ + match: nil, + anyhandler: handler, + }) + return p +} + +func (p *plan) Finally(f func()) *plan { + f2 := p.finally + p.finally = func() { + f() + f2() + } + return p +} + +func (p *plan) Done() { + defer func() { + rec := recover() + consumed := false + defer func() { + p.finally() + if !consumed { + panic(rec) + } + }() + switch err := rec.(type) { + case nil: + consumed = true + return + case *errors.Error: + // record the origin location of the error. + // this is redundant at first, but useful if the error is rethrown; + // then it shows line of the panic that rethrew it. + errors.RecordBefore(err, 3) + // run all checks + for _, catch := range p.catch { + if catch.match == nil { + consumed = true + catch.anyhandler(err) + return + } + if err.Is(catch.match) { + consumed = true + catch.handler(err) + return + } + } + case error: + // grabbag error, so skip all the typed catches, but still do wildcards and finally. + for _, catch := range p.catch { + if catch.match == nil { + consumed = true + catch.anyhandler(err) + return + } + } + default: + // if it's not even an error type, you're a fruit: get outta here. + return + } + }() + p.main() +} diff --git a/try/try_stack_test.go b/try/try_stack_test.go new file mode 100644 index 0000000..24c771b --- /dev/null +++ b/try/try_stack_test.go @@ -0,0 +1,54 @@ +package try_test + +import ( + "fmt" + "path/filepath" + "runtime" + "testing" + + "github.com/spacemonkeygo/errors" + "github.com/spacemonkeygo/errors/try" +) + +func TestStackHandling(t *testing.T) { + // Crappy test. Try harder later. + try.Do(func() { + try.Do(func() { + fmt.Println("function called") + panic(AppleError.New("emsg")) + }).Finally(func() { + fmt.Println("finally block called") + }).Catch(FruitError, func(e *errors.Error) { + fmt.Println("fruit handler called") + panic(e) + }).Done() + }).CatchAll(func(e error) { + fmt.Println("exit route:") + fmt.Println(errors.GetExits(e)) + fmt.Println() + fmt.Println("recorded stack:") + fmt.Println(errors.GetStack(e)) + fmt.Println() + + fmt.Println("final stack:") + var pcs [256]uintptr + amount := runtime.Callers(3, pcs[:]) + for i := 0; i < amount; i++ { + fmt.Println(frameStringer(pcs[i])) + } + fmt.Println() + + }).Done() +} + +func frameStringer(pc uintptr) string { + if pc == 0 { + return "unknown.unknown:0" + } + f := runtime.FuncForPC(pc) + if f == nil { + return "unknown.unknown:0" + } + file, line := f.FileLine(pc) + return fmt.Sprintf("%s:%s:%d", f.Name(), filepath.Base(file), line) +} diff --git a/try/try_test.go b/try/try_test.go new file mode 100644 index 0000000..cf0a64d --- /dev/null +++ b/try/try_test.go @@ -0,0 +1,151 @@ +package try_test + +import ( + "fmt" + + "github.com/spacemonkeygo/errors" + "github.com/spacemonkeygo/errors/try" +) + +func ExampleNormalFlow() { + try.Do(func() { + fmt.Println("function called") + }).Finally(func() { + fmt.Println("finally block called") + }).CatchAll(func(_ error) { + fmt.Println("catch wildcard called") + }).Done() + + // Output: + // function called + // finally block called +} + +func ExampleErrorInTry() { + try.Do(func() { + fmt.Println("function called") + panic(fmt.Errorf("any error")) + }).Finally(func() { + fmt.Println("finally block called") + }).CatchAll(func(_ error) { + fmt.Println("catch wildcard called") + }).Done() + + // Output: + // function called + // catch wildcard called + // finally block called +} + +func ExampleCrashInCatch() { + try.Do(func() { + try.Do(func() { + fmt.Println("function called") + panic(fmt.Errorf("any error")) + }).Finally(func() { + fmt.Println("finally block called") + }).CatchAll(func(_ error) { + fmt.Println("catch wildcard called") + panic(fmt.Errorf("zomg")) + }).Done() + }).CatchAll(func(e error) { + fmt.Println("outer error caught:", e.Error()) + }).Done() + + // Output: + // function called + // catch wildcard called + // finally block called + // outer error caught: zomg +} + +func ExampleErrorsLeaveFinally() { + try.Do(func() { + try.Do(func() { + fmt.Println("function called") + panic(fmt.Errorf("inner error")) + }).Finally(func() { + fmt.Println("finally block called") + }).Done() + }).CatchAll(func(e error) { + fmt.Println("outer error caught:", e.Error()) + }).Done() + + // Output: + // function called + // finally block called + // outer error caught: inner error +} + +func ExampleCrashInFinally() { + try.Do(func() { + try.Do(func() { + fmt.Println("function called") + }).Finally(func() { + fmt.Println("finally block called") + panic(fmt.Errorf("zomg")) + }).CatchAll(func(_ error) { + fmt.Println("catch wildcard called") + }).Done() + }).CatchAll(func(e error) { + fmt.Println("outer error caught:", e.Error()) + }).Done() + + // Output: + // function called + // finally block called + // outer error caught: zomg +} + +var FruitError = errors.NewClass("fruit") +var AppleError = FruitError.NewClass("apple") +var GrapeError = FruitError.NewClass("grape") +var RockError = errors.NewClass("rock") + +func ExampleCatchingErrorsByType() { + try.Do(func() { + try.Do(func() { + fmt.Println("function called") + panic(AppleError.New("emsg")) + }).Finally(func() { + fmt.Println("finally block called") + }).Catch(RockError, func(e *errors.Error) { + fmt.Println("rock handler called") + }).Catch(FruitError, func(e *errors.Error) { + fmt.Println("fruit handler called") + }).CatchAll(func(_ error) { + fmt.Println("catch wildcard called") + }).Done() + }).CatchAll(func(e error) { + fmt.Println("outer error caught:", e.Error()) + }).Done() + + // Output: + // function called + // fruit handler called + // finally block called +} + +func ExampleCatchingErrorsBySpecificType() { + try.Do(func() { + try.Do(func() { + fmt.Println("function called") + panic(AppleError.New("emsg")) + }).Finally(func() { + fmt.Println("finally block called") + }).Catch(AppleError, func(e *errors.Error) { + fmt.Println("apple handler called") + }).Catch(FruitError, func(e *errors.Error) { + fmt.Println("fruit handler called") + }).CatchAll(func(_ error) { + fmt.Println("catch wildcard called") + }).Done() + }).CatchAll(func(e error) { + fmt.Println("outer error caught:", e.Error()) + }).Done() + + // Output: + // function called + // apple handler called + // finally block called +} From 6758c0df059ede02122d3df2f5d391f6226fe8cb Mon Sep 17 00:00:00 2001 From: Eric Myhre Date: Thu, 5 Mar 2015 13:54:27 -0600 Subject: [PATCH 2/6] Export try.Plan. There's no good reason to, since you can never meaningfully interact with it other than through the already exposed chainable functions, but exporting it makes godoc do a significantly better job on the package. --- try/try.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/try/try.go b/try/try.go index 7638a45..67a5bb3 100644 --- a/try/try.go +++ b/try/try.go @@ -53,7 +53,7 @@ import ( "github.com/spacemonkeygo/errors" ) -type plan struct { +type Plan struct { main func() catch []check finally func() @@ -65,11 +65,11 @@ type check struct { anyhandler func(err error) } -func Do(f func()) *plan { - return &plan{main: f, finally: func() {}} +func Do(f func()) *Plan { + return &Plan{main: f, finally: func() {}} } -func (p *plan) Catch(kind *errors.ErrorClass, handler func(err *errors.Error)) *plan { +func (p *Plan) Catch(kind *errors.ErrorClass, handler func(err *errors.Error)) *Plan { p.catch = append(p.catch, check{ match: kind, handler: handler, @@ -77,7 +77,7 @@ func (p *plan) Catch(kind *errors.ErrorClass, handler func(err *errors.Error)) * return p } -func (p *plan) CatchAll(handler func(err error)) *plan { +func (p *Plan) CatchAll(handler func(err error)) *Plan { p.catch = append(p.catch, check{ match: nil, anyhandler: handler, @@ -85,7 +85,7 @@ func (p *plan) CatchAll(handler func(err error)) *plan { return p } -func (p *plan) Finally(f func()) *plan { +func (p *Plan) Finally(f func()) *Plan { f2 := p.finally p.finally = func() { f() @@ -94,7 +94,7 @@ func (p *plan) Finally(f func()) *plan { return p } -func (p *plan) Done() { +func (p *Plan) Done() { defer func() { rec := recover() consumed := false From 918d306278ab72242ec41098a280229894e1dad3 Mon Sep 17 00:00:00 2001 From: Nathaniel Kofalt Date: Sat, 7 Mar 2015 14:19:08 -0600 Subject: [PATCH 3/6] Add support for catching & handling panics of mundane types, such as int and string. --- try/try.go | 31 +++++++++- try/try_test.go | 149 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 2 deletions(-) diff --git a/try/try.go b/try/try.go index 67a5bb3..5048fe3 100644 --- a/try/try.go +++ b/try/try.go @@ -50,9 +50,20 @@ package try import ( + "fmt" + "github.com/spacemonkeygo/errors" ) +var ( + // Panic type when a panic is caught that is neither a spacemonkey error, nor an ordinary golang error. + // For example, panic("hooray!") + UnknownPanicError = errors.NewClass("Unknown Error") + + // The spacemonkey error key to get the original data out of an UnknownPanicError. + OriginalPanic = errors.GenSym() +) + type Plan struct { main func() catch []check @@ -136,8 +147,24 @@ func (p *Plan) Done() { } } default: - // if it's not even an error type, you're a fruit: get outta here. - return + // handle the case where it's not even an error type. + // we'll wrap your panic in an UnknownPanicError and add the original as data for later retrieval. + for _, catch := range p.catch { + if catch.match == nil { + consumed = true + msg := fmt.Sprintf("%v", rec) + pan := UnknownPanicError.NewWith(msg, errors.SetData(OriginalPanic, rec)) + catch.anyhandler(pan) + return + } + if UnknownPanicError.Is(catch.match) { + consumed = true + msg := fmt.Sprintf("%v", rec) + pan := UnknownPanicError.NewWith(msg, errors.SetData(OriginalPanic, rec)) + catch.handler(pan.(*errors.Error)) + return + } + } } }() p.main() diff --git a/try/try_test.go b/try/try_test.go index cf0a64d..667ddf5 100644 --- a/try/try_test.go +++ b/try/try_test.go @@ -149,3 +149,152 @@ func ExampleCatchingErrorsBySpecificType() { // apple handler called // finally block called } + +func ExampleIntPanic() { + try.Do(func() { + fmt.Println("function called") + panic(3) + }).Finally(func() { + fmt.Println("finally block called") + }).CatchAll(func(_ error) { + fmt.Println("catch wildcard called") + }).Done() + + // Output: + // function called + // catch wildcard called + // finally block called +} + +func ExampleIntOriginalPanic() { + try.Do(func() { + fmt.Println("function called") + panic(3) + }).Finally(func() { + fmt.Println("finally block called") + }).CatchAll(func(e error) { + data := errors.GetData(e, try.OriginalPanic) + fmt.Println("catch wildcard called:", data) + switch data.(type) { + case int: + fmt.Println("type is int") + } + }).Done() + + // Output: + // function called + // catch wildcard called: 3 + // type is int + // finally block called +} + +func ExampleCatchingUnknownErrorsByType() { + try.Do(func() { + fmt.Println("function called") + panic(3) + }).Finally(func() { + fmt.Println("finally block called") + }).Catch(RockError, func(e *errors.Error) { + fmt.Println("catch a rock") + }).Catch(try.UnknownPanicError, func(e *errors.Error) { + data := errors.GetData(e, try.OriginalPanic) + fmt.Println("catch UnknownPanicError called:", data) + switch data.(type) { + case int: + fmt.Println("type is int") + } + }).CatchAll(func(e error) { + fmt.Println("catch wildcard called") + }).Done() + + // Output: + // function called + // catch UnknownPanicError called: 3 + // type is int + // finally block called +} + +func ExampleStringPanic() { + try.Do(func() { + fmt.Println("function called") + panic("hey") + }).Finally(func() { + fmt.Println("finally block called") + }).CatchAll(func(_ error) { + fmt.Println("catch wildcard called") + }).Done() + + // Output: + // function called + // catch wildcard called + // finally block called +} + +func ExampleStringEscalatingPanic() { + try.Do(func() { + try.Do(func() { + fmt.Println("function called") + panic("hey") + }).Finally(func() { + fmt.Println("finally block called") + }).Done() + }).CatchAll(func(e error) { + fmt.Println("outer error caught") + }).Done() + + // Output: + // function called + // finally block called + // outer error caught +} + +func ExampleStringCrashInFinally() { + try.Do(func() { + try.Do(func() { + fmt.Println("function called") + }).Finally(func() { + fmt.Println("finally block called") + panic("hey") + }).CatchAll(func(_ error) { + fmt.Println("catch wildcard called") + }).Done() + }).CatchAll(func(e error) { + fmt.Println("outer error caught") + }).Done() + + // Output: + // function called + // finally block called + // outer error caught +} + +func ExampleStringRethrowInFinally() { + try.Do(func() { + try.Do(func() { + fmt.Println("function called") + panic("hey") + }).Finally(func() { + fmt.Println("finally block called") + }).Catch(try.UnknownPanicError, func(e *errors.Error) { + data := errors.GetData(e, try.OriginalPanic) + fmt.Println("catch UnknownPanicError called:", data) + switch data.(type) { + case string: + fmt.Println("type is string") + } + + panic(data) + }).CatchAll(func(_ error) { + fmt.Println("catch wildcard called") + }).Done() + }).CatchAll(func(e error) { + fmt.Println("outer error caught") + }).Done() + + // Output: + // function called + // catch UnknownPanicError called: hey + // type is string + // finally block called + // outer error caught +} From f313887934ef7927b7373c604f8b095f1bc1966b Mon Sep 17 00:00:00 2001 From: Eric Myhre Date: Thu, 26 Mar 2015 10:39:39 -0500 Subject: [PATCH 4/6] Add convenience function for repanicing and maintaining the original object, even when handling non-error types such as int and string. --- try/try.go | 29 +++++++++++++++++++++++++++++ try/try_test.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/try/try.go b/try/try.go index 5048fe3..414e8df 100644 --- a/try/try.go +++ b/try/try.go @@ -169,3 +169,32 @@ func (p *Plan) Done() { }() p.main() } + +/* + Panics again with the original error. + + If the error is a `UnknownPanicError` (i.e., a `CatchAll` block that's handling something + that wasn't originally an `error` type, so it was wrapped), it will unwrap that re-panic + with that original error -- in other words, this is a "do the right thing" method in all scenarios. + + You may simply `panic(err)` for all other typed `Catch` blocks (since they are never wraps), + though there's no harm in using `Repanic` consistently if you prefer. It is also safe to + use `Repanic` on other non-`error` types (though you're really not helping anyone with that + behavior) and objects that didn't originally come in as the handler's error. No part of + the error handling or finally handlers chain will change execution order as a result; + `panic` and `Repanic` cause identical behavior in that regard. +*/ +func Repanic(err error) { + wrapper, ok := err.(*errors.Error) + if !ok { + panic(err) + } + if !wrapper.Is(UnknownPanicError) { + panic(err) + } + data := errors.GetData(err, OriginalPanic) + if data == nil { + panic(errors.ProgrammerError.New("misuse of try internals", errors.SetData(OriginalPanic, err))) + } + panic(data) +} diff --git a/try/try_test.go b/try/try_test.go index 667ddf5..c6131d8 100644 --- a/try/try_test.go +++ b/try/try_test.go @@ -268,7 +268,7 @@ func ExampleStringCrashInFinally() { // outer error caught } -func ExampleStringRethrowInFinally() { +func ExampleStringRepanicCoversFinally() { try.Do(func() { try.Do(func() { fmt.Println("function called") @@ -298,3 +298,31 @@ func ExampleStringRethrowInFinally() { // finally block called // outer error caught } + +// this is a particularly useful pattern for doing cleanup on error, but not on success. +func ExampleObjectRepanicOriginal() { + obj := struct{}{} + try.Do(func() { + try.Do(func() { + fmt.Println("function called") + panic(obj) + }).Finally(func() { + fmt.Println("finally block called") + }).CatchAll(func(e error) { + fmt.Println("catch wildcard called") + // repanic... with the original error! + try.Repanic(e) + }).Done() + }).CatchAll(func(e error) { + // this example is a little funny, because it got re-wrapped again + // but the important part is yes, the (pointer equal!) object is in there. + data := errors.GetData(e, try.OriginalPanic) + fmt.Printf("outer error equals original: %v\n", data == obj) + }).Done() + + // Output: + // function called + // catch wildcard called + // finally block called + // outer error equals original: true +} From 491d8eedee222dccde437e1410af7f9bf7aedac8 Mon Sep 17 00:00:00 2001 From: Eric Myhre Date: Wed, 6 May 2015 16:21:26 -0500 Subject: [PATCH 5/6] Additional features for wrapped try errors. Add OriginalError convenience accessor. Rename data key var to make more sense. Add package level documentation pointing out CatchAll's error wrapping behavior. --- try/try.go | 33 ++++++++++++++++++++++++++------- try/try_test.go | 8 ++++---- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/try/try.go b/try/try.go index 414e8df..ae1acb8 100644 --- a/try/try.go +++ b/try/try.go @@ -33,8 +33,10 @@ (no special treatment; they'll hit `CatchAll` blocks and `Finally` blocks; it would of course be silly for them to hit `Catch` blocks since those use spacemonkey-errors types). Panics with values that are not of golang's - `error` type at all will trigger `Finally` blocks but otherwise - be immediately repanicked. + `error` type at all will be wrapped in a spacemonkey error for the purpose + of handling (as an interface design, `CatchAll(interface{})` is unpleasant). + See `OriginalError` and `Repanic` for more invormation on handling these + wrapped panic objects. A `try.Do(func() {...})` with no attached errors handlers is legal but pointless. A `try.Do(func() {...})` with no `Done()` will never run the @@ -61,7 +63,7 @@ var ( UnknownPanicError = errors.NewClass("Unknown Error") // The spacemonkey error key to get the original data out of an UnknownPanicError. - OriginalPanic = errors.GenSym() + OriginalErrorKey = errors.GenSym() ) type Plan struct { @@ -153,14 +155,14 @@ func (p *Plan) Done() { if catch.match == nil { consumed = true msg := fmt.Sprintf("%v", rec) - pan := UnknownPanicError.NewWith(msg, errors.SetData(OriginalPanic, rec)) + pan := UnknownPanicError.NewWith(msg, errors.SetData(OriginalErrorKey, rec)) catch.anyhandler(pan) return } if UnknownPanicError.Is(catch.match) { consumed = true msg := fmt.Sprintf("%v", rec) - pan := UnknownPanicError.NewWith(msg, errors.SetData(OriginalPanic, rec)) + pan := UnknownPanicError.NewWith(msg, errors.SetData(OriginalErrorKey, rec)) catch.handler(pan.(*errors.Error)) return } @@ -170,9 +172,26 @@ func (p *Plan) Done() { p.main() } +/* + If `err` was originally another value coerced to an error by `CatchAll`, + this will return the original value. Otherwise, it returns the same error + again. + + See also `Repanic()`. +*/ +func OriginalError(err error) interface{} { + data := errors.GetData(err, OriginalErrorKey) + if data == nil { + return err + } + return data +} + /* Panics again with the original error. + Shorthand, equivalent to calling `panic(OriginalError(err))` + If the error is a `UnknownPanicError` (i.e., a `CatchAll` block that's handling something that wasn't originally an `error` type, so it was wrapped), it will unwrap that re-panic with that original error -- in other words, this is a "do the right thing" method in all scenarios. @@ -192,9 +211,9 @@ func Repanic(err error) { if !wrapper.Is(UnknownPanicError) { panic(err) } - data := errors.GetData(err, OriginalPanic) + data := errors.GetData(err, OriginalErrorKey) if data == nil { - panic(errors.ProgrammerError.New("misuse of try internals", errors.SetData(OriginalPanic, err))) + panic(errors.ProgrammerError.New("misuse of try internals", errors.SetData(OriginalErrorKey, err))) } panic(data) } diff --git a/try/try_test.go b/try/try_test.go index c6131d8..797c2b6 100644 --- a/try/try_test.go +++ b/try/try_test.go @@ -173,7 +173,7 @@ func ExampleIntOriginalPanic() { }).Finally(func() { fmt.Println("finally block called") }).CatchAll(func(e error) { - data := errors.GetData(e, try.OriginalPanic) + data := errors.GetData(e, try.OriginalErrorKey) fmt.Println("catch wildcard called:", data) switch data.(type) { case int: @@ -197,7 +197,7 @@ func ExampleCatchingUnknownErrorsByType() { }).Catch(RockError, func(e *errors.Error) { fmt.Println("catch a rock") }).Catch(try.UnknownPanicError, func(e *errors.Error) { - data := errors.GetData(e, try.OriginalPanic) + data := errors.GetData(e, try.OriginalErrorKey) fmt.Println("catch UnknownPanicError called:", data) switch data.(type) { case int: @@ -276,7 +276,7 @@ func ExampleStringRepanicCoversFinally() { }).Finally(func() { fmt.Println("finally block called") }).Catch(try.UnknownPanicError, func(e *errors.Error) { - data := errors.GetData(e, try.OriginalPanic) + data := errors.GetData(e, try.OriginalErrorKey) fmt.Println("catch UnknownPanicError called:", data) switch data.(type) { case string: @@ -316,7 +316,7 @@ func ExampleObjectRepanicOriginal() { }).CatchAll(func(e error) { // this example is a little funny, because it got re-wrapped again // but the important part is yes, the (pointer equal!) object is in there. - data := errors.GetData(e, try.OriginalPanic) + data := errors.GetData(e, try.OriginalErrorKey) fmt.Printf("outer error equals original: %v\n", data == obj) }).Done() From 0b05cac8509e79700959e44f6cf22fbb1f9094d2 Mon Sep 17 00:00:00 2001 From: Eric Myhre Date: Wed, 6 May 2015 16:22:35 -0500 Subject: [PATCH 6/6] Package docs on appropriate use (as well as not). --- try/try.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/try/try.go b/try/try.go index ae1acb8..3f38515 100644 --- a/try/try.go +++ b/try/try.go @@ -48,6 +48,19 @@ complete picture. Note that 'finally' blocks will never be recorded (unless of course they raise a new error!), since they are functions that return normally. + + A note about use cases: while `try` should be familiar and comfortable + to users of exceptions in other languages, and we feel use of a "typed" + panic mechanism results in effective error handling with a minimization + of boilerplate checking error return codes... `try` and `panic` are not + always the correct answer! There are at least two situations where you + may want to consider converting back to handling errors as regular values: + If passing errors between goroutines, of course you need to pass them + as values. Another situation where error returns are more capable than + panics is when part of multiple-returns, where the other values may have + been partially assembled and still may need be subject to cleanup by the + caller. Fortunately, you can always use `CatchAll` to easily fence a block + of panic-oriented code and convert it into errors-by-value flow. */ package try