-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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: Go2: introduce check keyword for errors on LHS of assignment #46655
Comments
This seems substantively the same as the last check proposal, except on the LHS instead of a magic expression on RHS. The reason the last proposal was rejected AFAICT was that it turned out that check messed up code coverage percentages deeply (ie a line will count as covered even with no testing of error handling!), and no one wanted to bite that bullet. This proposal is not sufficiently different to get past that limitation, so I don't think it has much chance of being accepted. |
@carlmjohnson If line coverage is a blocker for error handling proposals, then I think it's fundamentally impossible for a language change that simultaneously removes the error handling boilerplate while making independent lines show up in a coverage report. That being said, I'm sympathetic to the idea that coverage tools should show if an error path is exercised. Any coverage tool/instrumentation/report would have to accommodate operations within a single line (or at least special-case the Also when you refer to the previous
desugars into
|
@gptankit Feel free to draft a separate proposal for that idea, but it has been brought up before: #33233 Defining built-in function carries the risks that it looks like a regular function call. Inspecting code that redefines One of the consequences behind this proposal is that an editor can highlight the |
I don't understand why this requires named result variables. Purely as a matter of style, the new use of |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I can update my proposal to mention these. However,
I should have explained this more in the original proposal, but when annotating the error result, the natural question to ask is "what happens when the error is non-nil?"
Edit: This is a tradeoff made for readability. Anything less than a keyword, and the code might be too cryptic because the function exits early. |
if you look at the code:
the error handling code tends to obscure the more important non-error code. |
I think better error-handling expr? // return if err |
@qingtao One of the goals of this proposal is to make it more obvious to the reader that there are changes to control flow. This is why I chose a keyword over an operator. If you have an argument for using a |
|
@qingtao Clearly there is a subjective element to this. If you feel that strongly about it, please file a separate proposal. That being said, on one hand @ianlancetaylor has stated that
while you state that
Therefore, I would not argue that a keyword and |
Something nice about Go is that general errors are simply values, the language doesn't give them any special treatment. This feature would contradict that idea. If we are to solve error handling, I would like it to be a solution that doesn't give errors special treatment. I'm alright with this solution, though. It does look a bit strange without syntax highlighting, however. Maybe my eyes just have to get used to it. |
It kind of does though. The |
Right, when I say “special treatment” I mean “behaves differently from other values”. Such as in Java with being able to throw Throwables and automatically unfurl the stack, or (not quite as drastically) Swift where errors have a special “slot” for functions to return an error. Currently, the Go spec does not have any “special treatment” for errors besides providing a universal interface for them. Using this interface is entirely optional, and the language itself doesn’t “encourage” its use in any way (ie special language features which only work on the builtin |
I believe most of the error handling proposals fall into this category, so this flaw is not unique to this proposal.
There are generalizations of this proposal that could be applied to non-error values, but I want to keep this narrow in scope. For example, However, I think even if errors were given special treatment, this is an example of a language evolving with the needs of users. Rather than introducing a type-level feature, we address the syntactic issue with a special form of assignment. I suspect some might wonder how this proposal would interact with generics, where functions may start returning
I believe Edit: One way I'm thinking about it is extending the definition of assignment to operate on errors. For pointers we have |
Yeah, pretty much all of them do. Not quite all though, however the ones that don't aren't very elegant... Not sure how possible it would be to do successfully.
I think it would be quite strange (at least with the spelling
This would be really cool provided that some kind of |
I almost always wrap my errors with a short message. In the example above I would have
I don't also see any problem with these ifs. With empty lines it looks pretty clear what's happening here. |
This proposal isn't meant to replace That being said, typically the context associated with a returned error is scoped at the function level, and not at each return. In the example you gave above, |
Would the |
@leighmcculloch |
It occurs to me that if something like Maybe something like the below, or that achieves the same effect as the below, but better because this seems not great: x, check("annotation %s: %w", someInput, err) := f() |
I don't know that it's true that adding context to every return is the most common case of error handling. I think if per-return context is needed, one would want to draw attention to it, and the two extra lines of control flow serve that purpose.
#33150 and #32500 suggest something similar. They are not very specific on evaluation order, and overlap with the work that error wrappers like Otherwise, writing the function so that in most cases, the context can be provided at the function-level would allow one to reuse mechanisms like |
As noted above, this is similar to previous proposals that were not accepted. It adds a new keyword, so it is not backward compatible. Based on that and the discussion above this is a likely decline. Leaving open for four weeks for final comments. |
Based on the upvote/downvote ratio, I can understand that this proposal is likely to be declined. However, for future reference, I'd like more clarity on why this language change is not considered backwards compatible. Although a new keyword is introduced, the keyword is not usable in a context where it is ambiguous. Typically, keywords are used in statements, and are not available on the left-hand side of an assignment. Moreover, there is no ambiguity with other identifiers on the left-hand side of an assignment, because currently it is not legal to have two identifiers next to each other on the left-hand side that are only separated by whitespace. |
By definition, a new keyword is not backward compatible. As the language spec says, a keyword may not be used as an identifier. Reading more closely, I see that although you say that There isn't anything like that in Go today. In Go identifiers are always defined, or not, in a given scope. The approach you describe raises a different kind of concern: identifiers that only sometimes have a special meaning can be confusing for programmers, because minor changes can have surprising repercussions. |
Understandable. I can imagine it being confusing for a programmer to see that x, check err := foo() and x, check := foo() have different semantics, although an editor highlighting |
No change in consensus. |
Would you consider yourself a novice, intermediate, or experienced Go programmer?
Experienced
What other languages do you have experience with?
C, C++, Assembly, Java, Python, Javascript, SML, Rust, Racket, Shell, SQL, LATEX.
Would this change make Go easier or harder to learn, and why?
It would make Go harder to learn, since it is one more choice the user must make before handling an error. Hopefully though, it becomes the default. It is also the first instance of an identifier being a keyword only in certain contexts. This could be confusing in cases where forgetting to bind
check
to a variable turns it into a variable.Has this idea, or one like it, been proposed before?
The proposal that comes closest to this idea is proposal: Go 2: allow to mark assigments of errors to return immediately when non-nil or to call a handler function #42318. Other similar issues are Proposal: Go 2: error handling by assignment, with named handlers #32500 proposal: Go 2: operator to cause early function return on error #32601 Proposal: Go2: try: Change try() proposal to use a special character in the position where an error is returned #32884 proposal: Go 2: use symbol or function call in assignment for error handling #33150.
If so, how does this proposal differ?
1. proposal: Go 2: allow to mark assigments of errors to return immediately when non-nil or to call a handler function #42318 uses a special character to mark the variable, whereas this proposal uses a new keyword.
2. proposal: Go 2: allow to mark assigments of errors to return immediately when non-nil or to call a handler function #42318 returns the zero value for unannotated variables, whereas this proposal relies on named return values to do the heavy lifting.
3. proposal: Go 2: allow to mark assigments of errors to return immediately when non-nil or to call a handler function #42318 allows substituting a handler function for the annotation, whereas this proposal makes use of defer for handlers.
4. Proposal: Go 2: error handling by assignment, with named handlers #32500 is far too broad in that it suggests panics, returns, and special operators, but doesn't specify evaluation order.
5. Proposal: Go2: try: Change try() proposal to use a special character in the position where an error is returned #32884, proposal: Go 2: operator to cause early function return on error #32601, and proposal: Go 2: use symbol or function call in assignment for error handling #33150 do not assign an identifier to the variable on the left-hand side, and don't specify how that variable is mapped to the return value, leaving us to assume that it relies on some convention around the last return value being an error. They also use an operator instead of a keyword, which obscures the early exit that can happen.
6. proposal: Go 2: use symbol or function call in assignment for error handling #33150 introduces a separate mechanism for wrapping an error.
Who does this proposal help, and why?
1. It helps authors of Go source code avoid three extra lines of code in the common case where they early exit on an error.
2. It helps readers of Go source code follow a function's core logic more easily, by collapsing the gap between two successive lines of logic.
3. Preserves the imperative style of Go source code. Unlike the try proposal, this proposal can't be abused to favor function composition.
What is the proposed change?
1. A new keyword
check
is introduced.2. The keyword is only valid on the left-hand-side of an assignment or short variable declaration.
3. The keyword is only valid if it precedes an identifier that refers to a variable of type
error
.4. The keyword can only be used in a function that has named return values.
5. In all other cases,
check
is a normal identifier. This keeps the change backwards-compatible to Go1.6. Given a function like this:
is equivalent to the following code:
Is this change backward compatible?
Yes
Show example code before and after the change.
Before
After (normal return) https://play.golang.org/p/nYugCFk4o-Z
After (return with additional context) https://play.golang.org/p/zaTO6KjUXmF
After (with a hypothetical error wrapper) https://play.golang.org/p/LQCn_Nj_I4C
What is the cost of this proposal? (Every language change has a cost).
As long as they upgrade to the latest libraries for parsing/type-checking packages, most of their static analyses should be unaffected. I'm unsure if there are outliers that check for weird properties on the LHS of an assignment, or if their error handling analyses that would trigger false positives because they expect
if err != nil
.Time-complexity-wise the same. Can be implemented entirely in the frontend.
Nothing
Can you describe a possible implementation?
I have a partial implementation on https://github.com/smasher164/check-go. I haven't had time to finish changes to the typechecker, so all it does right now is tokenize and parse the
check
keyword on the LHS of an assignment.Does this affect error handling?
Yes
Why are named return values necessary?
When annotating the error result, the natural question to ask is "what happens when the error is non-nil?"
Edit:
check
being a context-dependent keyword is different from the way identifiers work today, and could potentially be confusing to work with.The text was updated successfully, but these errors were encountered: