Description
What
handle
must be deferred and only in a function whose last return value is type error
. For brevity, I'll refer to this value as "the error" of the function.
handle
takes an argument of type assignable to func(error) error
. For brevity, I'll refer to this argument as an "error transformer".
If the error is nil
, the error transformer is not called. If the error is not nil
, the error transformer is called and its result replaces the error. Essentially:
if err != nil {
err = transform(err)
}
Why?
This allows error handling routines to be written as library code. For example
func wrap(msg string) func(error) error {
msg += ": %w"
return func(err error) error {
return fmt.Errorf(msg, err)
}
}
which can be used like
defer handle(wrap("additional context"))
How?
This can be done via a macro expansion like mechanism in the compiler.
Given
func example() error {
defer handle(wrap("oops"))
//...
}
the compiler can treat it as if the code was
func example() (the_error error) {
defer handle_impl(&the_error, wrap("oops"))
//...
}
func handle_impl(err *error, f func(error) error) {
if *err != nil {
*err = f(*err)
}
}
Alternatives
This could just be a regular function like:
package errors
func Handle(err *error, f func(error) error) { //...
That has two downsides:
- the invocation is even longer
- it's up to the user to pass the correct error which requires naming the return when otherwise unnecessary and making sure that name doesn't get shadowed.
A builtin obviates these, making it easier to use and thus more likely to be used.
This could be a keyword like
handle func(err error) error {
return fmt.Errorf("oops: %w", err)
}
which is obviously much better but not backwards compatible.
A func returnedError() *error
builtin that returns a pointer to the error of the deferring function. This is more powerful. You could use it to write handle
and other things. It's a bit too magical and harder to use.