A new error handling syntax

Proposals for new error handling syntaxes are a dime a dozen, but this one is different.

The Ultimate Guide to Debugging With Go
Learn debugging with Matt, at 40% off
(and support AppliedGo this way)!
Use the coupon code APPLIEDGO40 at ByteSizeGo.com.
(Affiliate Link)

Ian Lance Taylor, member of the core Go team, created a proposal that's been turned into a discussion shortly after:

discussion: spec: reduce error handling boilerplate using ? · golang/go · Discussion #71460

No, there is no word missing, the question mark is the item that shall reduce boilerplate. In a nutshell, the new syntax looks like so:

For functions that return a result and an error:

// proposed syntax:
r := SomeFunction() ? {
	return fmt.Errorf(...)
}

// shall replace (existing syntax):
r, err := SomeFunction()
if err != nil {
	return fmt.Errorf(...)
}

For functions that only return an error:

// proposed:
SomeFunction2() ? {
	return fmt.Errorf(...)
}

// shall replace:
if err := SomeFunction2(); err != nil {
	return fmt.Errorf(...)
}

For functions that return an error that shall be passed to the caller unwrapped:

// proposed:
SomeFunction2() ?

// shall replace:
if err := SomeFunction2(); err != nil {
	return err
}

Special case, as err can be treated as an expression statement whose last value is of type error, for cases where the error test is not adjacent to the error-producing function(s):

// proposed:
err ? {
	return fmt.Errorf(...)
}

// shall replace:
if err != nil {
	return fmt.Errorf(...)
}

I won't take sides. I am fine-ish with the old and the new syntax. If you're still unsure how this syntax affects real-life code, Ian converted the complete standard library to the new syntax so you can see for yourself:

all: demonstration of new error handling syntax for #71203 (644076) · Gerrit Code Review

(Line 124 in cover.go, for example, demonstrates the use of err ?.)

What I'm worried about is a shift in behavior resulting from the new syntax.

The problem? Naked returns become far too easy:

someFunction() ?

I wouldn't want to see codebases where errors just bubble up, from the low-level source of the error to the top where it gets logged.

Here is why: Years ago, when I worked in tech support, customers reported an error in their log files that said,

Generic SSA NOTOK

There was no context, nothing whatsoever that could reveal the reason for this error. So I turned to someone from the engineering team and asked him to help me understand what kind of malfunction triggers this error message.

He replied that the error is very well known to them, but it is called from many different parts of the application, hence he cannot determine the specific circumstances that run into this error.

The last thing I'd want to see in a Go app is the equivalent of “Generic SSA NOTOK”. As Go developers, we need to get into a habit of consciously determining if a given error needs to be passed along with contextual information.

I'm not saying that each and every error must be wrapped before returning, but as a bare minimum, errors should be wrapped if they occur at the boundaries of your code; that is, where your code calls other code or returns values to other code.

  1. Inbound boundary: A call into a library or an external API returns an error.
  2. Outbound boundary: Your library code returns an error to a caller.

In both cases, wrap the error with contextual information before passing it on.

What happens within your code is up to you. If your code contains internal call chains, no matter how long, it is ok if you let errors just bubble up internally, as long as the error carries enough context to enable you to track it down.