Now that Go has generics, I discovered that you can implement ergonomic exception-like error handling as a library. Here's my take on the idea:
https://github.com/chrismwendt/go-exceptions
Typical error-handling in Go looks like this:
func f() error {
err := g1()
if err != nil {
return err
}
v1, err := g2()
if err != nil {
return err
}
// ...
}
There are a few downsides to handling errors like this:
-
if err != nil { return err }
takes 3 extra lines - You need to explicitly store the errors in variables and conditionally return them every time you call a function that returns an error
- Error handling ends up cluttering the code and making it harder to focus on the domain logic
- You can't do
f(g())
ifg()
returns a value and an error andf()
expects only a value, which forces you to split up the code, add temporary variables, add anif
, and manually handle the error
How can we make this more ergonomic? Perhaps ideally, Go would add support for Rust's ?
operator, but we can get pretty close using panic()
and recover()
to have errors bubble up without needing to explicitly store them in variables.
Here's what usage of go-exceptions looks like:
import ex "github.com/chrismwendt/go-exceptions"
func f() (err error) {
// Catch() calls recover() and assigns the error to &err
defer ex.Catch(&err)
// Throw() calls panic() if the error argument is not nil
ex.Throw(g1())
// Throw() also accepts a label
ex.Throw(g2(), "g2")
// Throw1() returns 1 value, Throw2() returns 2 values, etc.
v1 := ex.Throw1(g3())
// Passing arguments is easier
v2 := ex.Throw1(g4(ex.Throw(g5())))
// ...
}
This is more ergonomic in a few ways:
- It eliminates a bunch of temporary
err
variables - You can more easily pass the result of one function into another
- The code is more compact
- Once you learn about the library, IMO the code is more readable
Internally:
-
Throw(err)
is basically:if err != nil { panic(err) }
. -
Catch(*error)
recovers from any panic and stores the recovered value in the given error
You could go a bit further and adopt a convention of panicking rather than returning errors, but that begins to depart from typical Go calling conventions and could cause friction when calling or being called by other code.
Top comments (0)