Yesterday Robert Griesemer posted a proposal for a new try built-in in Go. I have read the design document twice now and I think I like it.

I am not entirely comfortable saying that.

Two years ago I wrote that explicit error handling was exactly one of the things I loved about Go. I still do. The interesting question here is whether returning an error unchanged really counts as handling it.

The proposal turns this:

f, err := os.Open(filename)
if err != nil {
	return nil, err
}

into this:

f := try(os.Open(filename))

If os.Open returns an error, try places it in the enclosing function’s error result and returns from that function. Otherwise it gives us the other result. Errors remain values, functions still say that they return errors, and there are no exceptions jumping across some unknowable number of stack frames.

That is the part I like. A surprising amount of Go code is not really handling an error at all. It is checking the error and immediately passing it to the caller. I love explicit error handling, but writing four lines does not make that decision four times more explicit.

And yet try feels wrong.

It looks like a function but it can return from the function that called it. That is not something ordinary Go functions can do. The control flow is hidden inside an expression, and Go has spent the last ten years making control flow painfully, wonderfully obvious. There is no return on the line. It just returns.

The proposed way to add context also takes some getting used to. Instead of wrapping each error where it happens, a function can use a deferred handler to decorate the returned error. Using the helper sketched in the design, it would look something like this:

func readConfig(name string) (cfg Config, err error) {
	defer fmt.HandleErrorf(&err, "read configuration %s", name)

	data := try(ioutil.ReadFile(name))
	try(json.Unmarshal(data, &cfg))
	return cfg, nil
}

I am not sure I love the named result or the indirection in the handler. But I do like being able to see the actual operation without each line being separated from the next by the same little error-return ceremony.

My guess is that this proposal will not pass. It is a small addition in terms of specification and implementation, but a radical change in how Go code reads. Those are often the hardest changes: technically tiny and culturally enormous.

I also suspect that, if it does not pass, we may look back several years from now and wonder why it seemed so frightening. New syntax always has the disadvantage of looking unfamiliar in every example. The existing syntax has had a decade to become invisible.

Maybe I am wrong and try will turn real programs into a collection of secret exits. But the idea behind it is sound: keep explicit errors, while admitting that forwarding an error is not the interesting part of error handling.

That seems worth trying.