Description
Proposal: A built-in Go error check function, try
This proposal has been closed. Thanks, everybody, for your input.
Before commenting, please read the detailed design doc and see the discussion summary as of June 6, the summary as of June 10, and most importantly the advice on staying focussed. Your question or suggestion may have already been answered or made. Thanks.
We propose a new built-in function called try
, designed specifically to eliminate the boilerplate if
statements typically associated with error handling in Go. No other language changes are suggested. We advocate using the existing defer
statement and standard library functions to help with augmenting or wrapping of errors. This minimal approach addresses most common scenarios while adding very little complexity to the language. The try
built-in is easy to explain, straightforward to implement, orthogonal to other language constructs, and fully backward-compatible. It also leaves open a path to extending the mechanism, should we wish to do so in the future.
[The text below has been edited to reflect the design doc more accurately.]
The try
built-in function takes a single expression as argument. The expression must evaluate to n+1 values (where n may be zero) where the last value must be of type error
. It returns the first n values (if any) if the (final) error argument is nil, otherwise it returns from the enclosing function with that error. For instance, code such as
f, err := os.Open(filename)
if err != nil {
return …, err // zero values for other results, if any
}
can be simplified to
f := try(os.Open(filename))
try
can only be used in a function which itself returns an error
result, and that result must be the last result parameter of the enclosing function.
This proposal reduces the original draft design presented at last year's GopherCon to its essence. If error augmentation or wrapping is desired there are two approaches: Stick with the tried-and-true if
statement, or, alternatively, “declare” an error handler with a defer
statement:
defer func() {
if err != nil { // no error may have occurred - check for it
err = … // wrap/augment error
}
}()
Here, err
is the name of the error result of the enclosing function. In practice, suitable helper functions will reduce the declaration of an error handler to a one-liner. For instance
defer fmt.HandleErrorf(&err, "copy %s %s", src, dst)
(where fmt.HandleErrorf
decorates *err
) reads well and can be implemented without the need for new language features.
The main drawback of this approach is that the error result parameter needs to be named, possibly leading to less pretty APIs. Ultimately this is a matter of style, and we believe we will adapt to expecting the new style, much as we adapted to not having semicolons.
In summary, try
may seem unusual at first, but it is simply syntactic sugar tailor-made for one specific task, error handling with less boilerplate, and to handle that task well enough. As such it fits nicely into the philosophy of Go. try
is not designed to address all error handling situations; it is designed to handle the most common case well, to keep the design simple and clear.
Credits
This proposal is strongly influenced by the feedback we have received so far. Specifically, it borrows ideas from:
- Key Parts of Error Handling,
- issue #31442
- and, related, issue #32219.
Detailed design doc
https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md
Activity
rasky commentedon Jun 4, 2019
I agree this is the best way forward: fixing the most common issue with a simple design.
I don't want to bikeshed (feel free to postpone this conversation), but Rust went there and eventually settled with the
?
postfix operator rather than a builtin function, for increased readability.The gophercon proposal cites
?
in the considered ideas and gives three reason why it was discarded: the first ("control flow transfers are as a general rule accompanied by keywords") and the third ("handlers are more naturally defined with a keyword, so checks should too") do not apply anymore. The second is stylistic: it says that, even if the postfix operator works better for chaining, it can still read worse in some cases like:rather than:
but now we would have:
which I think it's clearly the worse of the three, as it's not even obvious anymore which is the main function being called.
So the gist of my comment is that all three reasons cited in the gophercon proposal for not using
?
do not apply to thistry
proposal;?
is concise, very readable, it does not obscure the statement structure (with its internal function call hierarchy), and it is chainable. It removes even more clutter from the view, while not obscuring the control flow more than the proposedtry()
already does.jimmyfrasche commentedon Jun 4, 2019
To clarify:
Does
return (0, "x") or (7, "x")? I'd assume the latter.
Does the error return have to be named in the case where there's no decoration or handling (like in an internal helper function)? I'd assume not.
ianlancetaylor commentedon Jun 5, 2019
Your example returns
7, errors.New("x")
. This should be clear in the full doc that will soon be submitted (https://golang.org/cl/180557).The error result parameter does not need to be named in order to use
try
. It only needs to be named if the function needs to refer to it in a deferred function or elsewhere.dominikh commentedon Jun 5, 2019
I am really unhappy with a built-in function affecting control flow of the caller. This is very unintuitive and a first for Go. I appreciate the impossibility of adding new keywords in Go 1, but working around that issue with magic built-in functions just seems wrong to me. It's worsened by the fact that built-ins can be shadowed, which drastically changes the waytry(foo)
behaves. Shadowing of other built-ins doesn't have results as unpredictable as control flow changing. It makes reading snippets of code without all of the context much harder.I don't like the way postfix?
looks, but I think it still beatstry()
. As such, I agree with @rasky .Edit: Well, I managed to completely forget that panic exists and isn't a keyword.
griesemer commentedon Jun 5, 2019
The detailed proposal is now here (pending formatting improvements, to come shortly) and will hopefully answer a lot of questions.
griesemer commentedon Jun 5, 2019
@dominikh The detailed proposal discusses this at length, but please note that
panic
andrecover
are two built-ins that affect control flow as well.nictuku commentedon Jun 5, 2019
One clarification / suggestion for improvement:
if the last argument supplied to try, of type error, is not nil, the enclosing function’s error result variable (...) is set to that non-nil error value before the enclosing function returns
Could this instead say
is set to that non-nil error value and the enclosing function returns
? (s/before/and)On first reading,
before the enclosing function returns
seemed like it would eventually set the error value at some point in the future right before the function returned - possibly in a later line. The correct interpretation is that try may cause the current function to return. That's a surprising behavior for the current language, so a clearer text would be welcomed.purpleidea commentedon Jun 5, 2019
I think this is just sugar, and a small number of vocal opponents teased golang about the repeated use of typing
if err != nil ...
and someone took it seriously. I don't think it's a problem. The only missing things are these two built-ins:https://github.com/purpleidea/mgmt/blob/a235b760dc3047a0d66bb0b9d63c25bc746ed274/util/errwrap/errwrap.go#L26
webermaster commentedon Jun 5, 2019
Not sure why anyone ever would write a function like this but what would be the envisioned output for
try(foobar())
If
foobar
returned(error, error)
dominikh commentedon Jun 5, 2019
I retract my previous concerns about control flow and I no longer suggest using
?
. I apologize for the knee-jerk response (though I'd like to point out this wouldn't have happened had the issue been filed after the full proposal was available).I disagree with the necessity for simplified error handling, but I'm sure that is a losing battle.
try
as laid out in the proposal seems to be the least bad way of doing it.ianlancetaylor commentedon Jun 5, 2019
@webermaster Only the last
error
result is special for the expression passed totry
, as described in the proposal doc.akyoto commentedon Jun 5, 2019
Like @dominikh, I also disagree with the necessity of simplified error handling.
It moves vertical complexity into horizontal complexity which is rarely a good idea.
If I absolutely had to choose between simplifying error handling proposals, though, this would be my preferred proposal.
cespare commentedon Jun 5, 2019
It would be helpful if this could be accompanied (at some stage of accepted-ness) by a tool to transform Go code to use
try
in some subset of error-returning functions where such a transformation can be easily performed without changing semantics. Three benefits occur to me:try
could be used in their codebase.try
lands in a future version of Go, people will likely want to change their code to make use of it. Having a tool to automate the easy cases will help a lot.try
will make it easy to examine the effects of the implementation at scale. (Correctness, performance, and code size, say.) The implementation may be simple enough to make this a negligible consideration, though.863 remaining items
mfatihercik commentedon Mar 11, 2024
Although the discussion on this issue is concluded, I'd like to introduce my library, which offers a solution to the problem we've been addressing through try proposal.
One notable question from the try design document is:
My library leverages generics to replicate try's functionality, circumventing these limitations. It introduces distinct functions for handling functions with different return counts. For instance, Check1 is designed for functions with a single return value, while Check2 caters to those with two. This approach simplify function calls, exemplified by:
I know this issue is closed but I think it wort to share my library here that addressing the issue that we trying to solve with try or catch
One of the question in the try design document is as follow:
I implemented a library that using generics to achieve the same functionality of the try. The difference in here for different return type I created a separate function.e.g Check1,work with function have 1 return, Check2 work with function have two return. In this way we can simplify calling functions
Example call will be:
To handle errors, the Check function triggers a panic. Errors can be caught using a defer statement alongside named returns in HandleReturn function to capture and assign the error.
Check function "panic" the error. Now we need to catch the error via defer statement.
We can use named return and HandleReturn function to catch error and set the error.
A complete usage example is as follows:
Additionally, custom error handlers can be used with the Check function to tailor error handling or add context to errors.
Custom error handler can be used as follow:
For more detailed examples and to explore the library further, please visit: https://github.com/mfatihercik/errless.
try
keyword forTry Calls
#68391zeroidentidad commentedon Aug 9, 2024
If it is a closed issue, why can it still be commented? There are a lot of comments over the years
ianlancetaylor commentedon Aug 9, 2024
People can still discuss the idea even though we aren't going to adopt it.
flysand7 commentedon Aug 10, 2024
invisiblepancake commentedon Apr 15, 2025