Skip to content

proposal: Go 2: simplify error handling with || err suffix #21161

Closed
@ianlancetaylor

Description

@ianlancetaylor

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:

  1. ignore the error
  2. return the error unmodified
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeLanguageChangeSuggested changes to the Go languageNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Proposalerror-handlingLanguage & library change proposals that are about error handling.v2An incompatible library change

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions