-
Notifications
You must be signed in to change notification settings - Fork 17.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: Go 2: allow to mark assigments of errors to return immediately when non-nil or to call a handler function #42318
Comments
Please note that you should fill https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing a language change. |
It seems as pointed out there have been similar proposals before to introduce handler functions and annotate variables to invoke those handlers and allow exiting based on conditions. See #40432 for umbrella issue. Not using a new builtin but a marker has been proposed before e.g.: #32884 One previous concern about single char markers has been: As this proposal does not seem to address the problem of hiding control flow could you elaborate what additional utility over previous proposals this proposal adds that makes it better then the previous ones with single character markers to not need to address that issue? |
@martisch Thanks for adding labels! I read the linked issues mentioned in the meta issue, and some more (Sidenote: link to #33241 seems to point to an unrelated issue). While my proposal certainly has overlapping aspects to various degrees with many other ones, especially from the marker char camp, I want to point out several observations.
I get the general criticism about proposals using mark characters, but I think it makes quite a difference to require an identifier like in #33074 and this proposal. I don't think there is too much magic about it, a meaningful identifier gives you all the hints about WHAT exactly is specially handled by a mark character. And if the character itself makes sense, that helps too. Other languages have special syntax constructs as well. I think if their presence can be justified and you give it a catchy name that is easy to remember (think of the elvis operator), it makes sense to include such syntax changes instead of introducing new keywords. So yeah, I think my proposal is both minimalist and powerful enough, somewhat like Go in general ;) |
See #33150 for another proposal in this general space. I want to stress what @martisch already mentioned: there have been strong objections to having a single character cause a change in control flow. And, to make it worse, the change in control flow is conditional. There just isn't any construct in Go today that works anything like that. |
For language change proposals, please fill out the template at https://go.googlesource.com/proposal/+/refs/heads/master/go2-language-changes.md . When you are done, please reply to the issue with Thanks! |
I wonder how these "assignment-marking" proposals would fare if instead of using a character like
Granted, this overloads the meaning of |
Consider me a benevolent observer.
Java, JavaScript, Pascal, TypeScript, P/L SQL (Oracle), SQL PL (IBM), Lisp, Objective-C.
There would be one more syntactical element to learn.
The idea of using marker characters to reduce boilerplate in error handling has been proposed before several times.
It helps to reduce boiler plate code in error handling significantly.
The design proposes a change to the syntax of Go to allow the ^ character as a prefix to an identifier on the left-hand side of
and only inside a function body.
Syntax rules for variable declarations with identifiers, short variable declarations and assignments need to be changed to allow the ^-character as a prefix to identifiers.
Yes, the syntax change is fully backwards compatible.
Before
After
Every tool parsing (and formatting) Go code needs to be adapted.
There is no compile time cost.
If handler functions are used, there could be costs due to copying of error parameters.
Prototype not available.
See above.
The change is just syntactical sugar for reducing boilerplate code.
No.
Answered in detail in this comment. |
@gopherbot please remove label WaitingForInfo |
Overloading the meaning of
|
Negative points of this approach (My own opinion)
|
In my code review this "infamous" if err != nil {
return err
} will likely to be asked: why no decoration. In my code reviews this is only allowed if there's just one return with error point in a whole function. In other cases you must decorate error with a description of what was really happening when you got an error. |
As someone who writes at least some production Go code, I don't think any of my production code contains only
In essentially all situations where my code returns an error, it will also emit logging information, at a suitable level (this could be informational, a warning, or an error), frequently also incrementing metrics counters and other things. From where I am sitting, this is not syntactic sugar that I see the need for, and I'd argue that it is at best neutral for Go. Now, I realise that this is possibly different from general-purpose libraries, where error-decorations are probably better. |
As with the rejected Based on that and the discussion above, this is a likely decline. Leaving open for four weeks for final comments. |
@ianlancetaylor I understand and would like to ask: From the Go team's perspective, could you please clarify what is considered 'hiding' the control flow and what isn't. I think that a clarification would be a valuable thing for any future proposals on the issue of error handling. |
Ordinary flow of control goes through function calls, from one statement to the next, and falling off the end of a function to return. Any other kind of flow control is unusual. In Go unusual flow control is always determined by a keyword like |
No change in consensus. |
In search for a solution to eliminate the infamous
3-liner, I came up with an intriguing idea: What if Go could let you mark any identifier on the left-hand side of an assignment or variable declaration to immediately return the new value, when it is != nil? While investigating this idea further, I eventually decided to cut it down to error handling as of now. You can read about the much broader approach in the section Where I’m coming from with this proposal – and where this could lead to, in case you are interested.
But let’s dive right into how this proposal could improve error handling in Go.
Use ^ to mark error identifiers or error handling functions
The design proposes a change to the syntax of Go to allow the ^ character as a prefix to an identifier on the left-hand side of
and only inside a function body.
Example of marking a returned value of type error in a short variable declaration:
When used in this context, the ^ character may be called shortcut return mark for now (see section Considerations regarding the naming of the feature for alternative names).
^-marked identifiers return if != nil
To put it in a formalized way: When the newly assigned value is != nil, the identifier’s new value will be returned.
As an example, this code
is a shorter version of
Using function identifiers to handle errors
When an error should not only be returned as it is, but needs to be wrapped or treated otherwise, the ^-mark should be allowed on a function value like in the following example:
The function behind
handleError
gets called, whenos.Open
returns an error (meaning it is != nil). The error is passed as a parameter to the handler function, which returns aSpecificError
in the example and that type implements the error interface. It is thus compatible with the return value of function doWork and supplies the value to be returned, an instance ofSpecificError
.More formally, a function value reference can be preceded by the ^-mark and turned into an error handler, if
What is returned for other return values of a function (anything but the error)?
If a function is being shortcut-returned by a ^-marked error or handler function, any return value other than the error should return its type’s zero-value. This matches the behavior of other proposals.
I think the exception should be named return values that already had a (non-zero) value assigned, that is: They should keep and return their already assigned value. But I put it up for debate what would be more of a gotcha: the implicit zeroing of an explicitly set return value or that a return value of a "failable" function is != it’s zero value when an error occurred. A handler function could always explicitly zero any named return value, though.
Allow an error handler function to return more than just the error
Following up on then previous section, it should be permitted for an error handling function to return not only the error to the function that uses the error handler, but all of its return values. That would e.g. allow handlers to supply fallback values when a certain type of error occurs. Example:
The function
printCurrentDirectoryCmd
should return the name of the command to print the current working directory: pwd in Unix-like environments, cd in Windows. First, pwd is tried. If the command is present, the function returns the name of the command and no error in the last line of the function body.When pwd cannot be found and an error occurred, the error handling function
handlePwdErr
is called. Look at its return values: it returns the same type asprintCurrentDirectoryCmd
and in the same order. The statement^handlePwdErr := cmd.Run()
thus returns both values fromhandlePwdErr
, which tries to do the same thing for the cd command. If cd isn’t found either, an error is returned byhandleCdErr
, along with an empty string (the zero-value of type string).Let an error handler function return a type other than an error
Let’s look at the example from the previous section again. Function
printCurrentDirectoryCmd
could as well just omit the error and state in its documentation that the returned command is empty, if no matching command could be found for the current execution environment. An error handling function should be allowed to return a type other than error, so the example could be rewritten like this:Semantics of ^-mark syntax
A return statement passes control (along with the return values) up the call stack to its callee. The same is true for a shortcut return. The ^ character is occasionally used to point to something above in various contexts, because it looks like the head of an arrow pointing upwards.
I could imagine that, in everyday talk, Gophers would say things like “just up-return the error” or “just up the err”, which would make perfect sense to me.
Considerations regarding the naming of the feature
I want to put the following four alternative names for this feature up for discussion:
Where I’m coming from with this proposal – and where this could lead to
As I mentioned before, in search for a solution to get rid of error handling boilerplate code I imagined an approach that would go way beyond handling errors. The idea would be that you could mark any identifier on the left-hand side of a (short) variable declaration or assignment using the ^ character to immediately return its value when the new value is unequal to the zero value of the type. How could this be useful? Let’s say you have multiple functions that wrap certain command line program calls and pass back their return codes of type int. By convention, 0 means that the command line program executed successfully. Calling such a series of command line programs - and aborting when one of them fails - could be written very elegantly using the ^-mark just like this:
I eventually discarded this idea for now, because it raised more and more questions, the longer I thought about it. And because I wasn’t sure what the implications of this language feature being widely available would have on Go code. Most importantly, how this would blur the lines between assignment, function calls and conditional program flow.
The good thing is: Allowing the ^-mark on any type, not just errors, could be brought to Go at a later point in time without the need for a breaking change.
Restricting the identifier name when shortcut-returning plain errors
It should be considered to allow only
err
as a valid identifier name when marking plain errors with the ^ character. Otherwise, small typos could lead to possibly hard to debug and identify errors like in the following example:Can you spot the typing error? In this case,
handleError
isn’t being called and the call toos.Open
returns its error as-is, which is unintended behavior.To prevent these kinds of programming mistakes, the first incarnation of the feature should allow the shortcut-retuning of plain-errors with
^err
only. This rule could be relaxed later, either when extending shortcut returns like described previously or when a general feeling develops in the Go community that this restriction isn’t necessary or somehow prevents better code.Resuming control
Other proposals like the one by Marcel van Lohuizen have no way of resuming control, once the error handling is underway. This proposal would have two ways of providing conditional shortcut-returning on errors which typically means inspecting them more deeply. The first approach is based on syntax introduced so far, and could be written in a 2-liner like this:
So, the first statement would simply capture the error. In the second statement, a handler function would be called, that conditionally checks the supplied error and returns an error or not, based on what is appropriate. Because the err on the left-hand side of the assignment is ^-marked, the statement would shortcut-return that error in the surrounding function if
handleErr
returns a value != nil.For the second idea, let’s look again at what the meaning of the ^-mark is: When used with a handler function, that function is called only when the error parameter is != nil. And errors are returned directly, when != nil. To combine and nest these two steps, a double ^-mark could be considered:
How to read this? Again, this is a nested statement, so the first thing happening is
^handleErr
, meaning that the handler function is only being called if the sole parameter is != nil. If the handler is called, it’s return value (an error) is evaluated a second time by the first of the two ^ characters in the above statement: It shortcut-returns the error returned byhandleErr
from the surrounding function.To recap:
Benefits
Downsides
The text was updated successfully, but these errors were encountered: