Skip to content

Commit

Permalink
catch nil panics without Throw
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-cantwell committed Feb 26, 2023
1 parent f48a6e1 commit 28e7d3e
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 27 deletions.
20 changes: 20 additions & 0 deletions _examples/catchthrow/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"fmt"

. "github.com/kevin-cantwell/exceptions"
)

func main() {
Try(func() {
Try(func() {
panic(int(123))
}, Catch(func(cause int) {
fmt.Println("caught:", cause)
panic("re-throw")
}))
}, Catch(func(cause string) {
fmt.Println("caught:", cause)
}))
}
9 changes: 5 additions & 4 deletions _examples/nilpanic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import (

func main() {
Try(func() {
// panic(nil) is undetectable. Just a "feature" of Go.
Throw(nil)
}, CatchNilThrows(func(cause any) {
fmt.Println("nil panic!", cause)
// Typically, nil panics are not detected at runtime. But we can detect them if they
// happen in the Try block.
panic(nil)
}, CatchNil(func(cause any) {
fmt.Printf("nil panic! %T\n", cause)
}), Finally(func() {
// gets called
fmt.Println("This will print after catching the error.")
Expand Down
34 changes: 11 additions & 23 deletions exceptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ package exceptions
// the panic is recovered and its value is passed to the first Catch block that matches
// the recovered type. If no suitable Catch is found, it panics with the recovered value.
func Try(try func(), thens ...then) {
var (
// remains true if and only if try panics
panicked bool = true
)
defer func() {
defer func() {
for _, then := range thens {
Expand All @@ -12,22 +16,20 @@ func Try(try func(), thens ...then) {
}
}
}()

// note: nil panics will not be detected unless wrapped with Throw
if cause := recover(); cause != nil {
if panicked {
cause := recover()
for _, then := range thens {
if then.catch != nil {
if caught := then.catch(cause); caught {
caught = true
return
}
}
}
panic(cause)
}
}()

try()
panicked = false
}

type then struct {
Expand All @@ -48,20 +50,19 @@ func Catch[C any](catch func(C)) then {
catch(c)
return true
}

return false
},
}
}

func CatchNilThrows(catch func(any)) then {
// CatchNil is a special catch block used for nil panics.
func CatchNil(catch func(any)) then {
return then{
catch: func(cause any) bool {
if np, ok := cause.(nilPanic); ok {
catch(np.cause)
if cause == nil {
catch(cause)
return true
}

return false
},
}
Expand All @@ -78,16 +79,3 @@ func Finally(finally func()) then {
finally: finally,
}
}

// Throw wraps a call to panic. If the cause itself is nil it will panic with a special type
// that wraps the nil. To detect nil Throws, use CatchNilThrows.
func Throw(cause any) {
if cause == nil {
panic(nilPanic{cause: cause})
}
panic(cause)
}

type nilPanic struct {
cause any
}

0 comments on commit 28e7d3e

Please sign in to comment.