Skip to content

proposal: spec: add "must" operator # to check and return error #18721

Closed
@jargv

Description

@jargv

The must operator is to error handling what the go keyword is to concurrency, and the defer keyword is to resource cleanup. It turns any function into a Must*-style function with smart error handling.

NOTE: this isn't a change to the way error handling works, only to the way error handling looks.

The Must operator (#) is a unary prefix operator that can be applied to a function call. The last return value of the called function must be of type error. The must operator "peels off" the returned error from the result list, and handles that error - if it is not nil - according to the error handling strategy of the surrounding function.

There are 2 possible error handling strategies:

  1. Return zero values, plus the error
  2. Panic with the error

The error handling strategy is determined by the surrounding function's return types: if the last return value is of type error, it's error handling strategy is case 1, otherwise it's case 2.

Example:

  result := #foo(1,2,3) 

  // would be equivalent to
  result, err := foo(1,2,3)
  if err != nil {
    return 0, "", person{}, err // if the surrounding function returns an error
  }

  // -- or --

  result, err := foo(1,2,3)
  if err != nil {
    panic(err) //if the surrounding function does not return an error
  }

Some Pros:

  1. Error handling works the same way as before, but can be written a bit more tersely
  2. A function could very easily change its error handling strategy without changing its contents
  3. Adding or removing return values would no longer cascade into code that returns early because of errors. This code very often (always?) returns zero values, plus the error
  4. Example code that seeks to be terse would stop using the "drop the error" idiom:
result, _ := canFail() // silent failure!
  1. Libraries would no longer have to write "Must*" variants of functions: any function could be called with the Must Operator
  2. This is a backwards compatible change
  3. This may discourage the use of panic, as an error propagation mechanism
  4. Certain kinds of error-heavy code could have a better signal-to-noise ratio, increasing readability:
average := #getTotal() / #getCount()
  1. The programmer still has to acknowledge that an error can happen, but can use the Must operator to handle it.

Some Cons:

  1. Tools would need to be updated
  2. This may seem cryptic to new Gophers
  3. This may encouraging more use of panic by hiding the difference between panic and returning an error
  4. This may discourage the cases where errors require additional data as they propagate up the call stack:
return fmt.Errorf("while deleting user %s: %v", user, err)
// -- or --
return &CompoundError{"reason", err}

Conclusion

I believe there's precedent for the Must operator in golang. The defer and go keywords transform the way function calls work. The go keyword hides a load of complexity from the user, but always does the right thing. The defer keyword makes code less error-prone by ensuring the cleanup code will be called on every path. The Must operator is similar: it does the right thing based on the surrounding function, and it ensures that the error is handled.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeLanguageChangeSuggested changes to the Go languageProposalerror-handlingLanguage & library change proposals that are about error handling.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions