Description
There have been many proposals for how to simplify error handling in Go, all based on the general complaint that too much Go code contains the lines
if err != nil {
return err
}
I'm not sure that there is a problem here to be solved, but since it keeps coming up, I'm going to put out this idea.
One of the core problems with most suggestions for simplifying error handling is that they only simplify two ways of handling errors, but there are actually three:
- ignore the error
- return the error unmodified
- return the error with additional contextual information
It is already easy (perhaps too easy) to ignore the error (see #20803). Many existing proposals for error handling make it easier to return the error unmodified (e.g., #16225, #18721, #21146, #21155). Few make it easier to return the error with additional information.
This proposal is loosely based on the Perl and Bourne shell languages, fertile sources of language ideas. We introduce a new kind of statement, similar to an expression statement: a call expression followed by ||
. The grammar is:
PrimaryExpr Arguments "||" Expression
Similarly we introduce a new kind of assignment statement:
ExpressionList assign_op PrimaryExpr Arguments "||" Expression
Although the grammar accepts any type after the ||
in the non-assignment case, the only permitted type is the predeclared type error
. The expression following ||
must have a type assignable to error
. It may not be a boolean type, not even a named boolean type assignable to error
. (This latter restriction is required to make this proposal backward compatible with the existing language.)
These new kinds of statement is only permitted in the body of a function that has at least one result parameter, and the type of the last result parameter must be the predeclared type error
. The function being called must similarly have at least one result parameter, and the type of the last result parameter must be the predeclared type error
.
When executing these statements, the call expression is evaluated as usual. If it is an assignment statement, the call results are assigned to the left-hand side operands as usual. Then the last call result, which as described above must be of type error
, is compared to nil
. If the last call result is not nil
, a return statement is implicitly executed. If the calling function has multiple results, the zero value is returned for all result but the last one. The expression following the ||
is returned as the last result. As described above, the last result of the calling function must have type error
, and the expression must be assignable to type error
.
In the non-assignment case, the expression is evaluated in a scope in which a new variable err
is introduced and set to the value of the last result of the function call. This permits the expression to easily refer to the error returned by the call. In the assignment case, the expression is evaluated in the scope of the results of the call, and thus can refer to the error directly.
That is the complete proposal.
For example, the os.Chdir
function is currently
func Chdir(dir string) error {
if e := syscall.Chdir(dir); e != nil {
return &PathError{"chdir", dir, e}
}
return nil
}
Under this proposal, it could be written as
func Chdir(dir string) error {
syscall.Chdir(dir) || &PathError{"chdir", dir, err}
return nil
}
I'm writing this proposal mainly to encourage people who want to simplify Go error handling to think about ways to make it easy to wrap context around errors, not just to return the error unmodified.