-
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 else err after function calls #65793
Comments
Please fill out https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing language changes |
What happens if the else block completes normally?
The scope of variable x starts after this complete statement, so you can't assign to it from the 'else' block. Are you proposing to change the scope rules too? This doesn't seem to add anything not already rejected in prior proposals (thanks @seankhliao for the links). |
Nothing special.
In my proposal any assignment happens just before the else scope is excuted (if so). So x can indeed be accessed in the else block. I've included an example of how it would translate to current syntax in the proposal.
|
I would say see #31442, but I think you're familiar with it. 😄 |
Well, something must happen because the postfix |
I think this proposal, as well as many others were based on the assumption that the error returning format Currently, returning the error by So I guess in order for proposal like this to be successful, the |
The original writeup compares an existing supported pattern of an declaration/assignment followed by an I think it's also interesting to compare the new proposal to the other currently-supported form where the declaration/assignment is embedded in the // map key lookup
if val, ok := m[key]; !ok {
// key is not found in the map
}
// type assertions
if rw, ok := r.(io.ReadWriter); !ok {
// r does not satisfy io.ReadWriter
}
// channel receiving
if val, ok := <-ch; !ok {
// channel closed
}
// file opening
if f, err := os.Open("readme"); err != nil {
// ...
} This shorthand has a similar "weight on the page" as the proposed new grammar. In this case the Comparing these two, the most significant difference seems to be that of scoping: when embedding a declaration in an if f, err := os.Open("readme"); err == nil {
// do something with f
} else {
// do something with err
} If the primary concerns of this proposal are conciseness and scoping, I wonder if there's a smaller change in finding a way to place A strawman follows. I don't particularly love this specific syntax but I'm including it to illustrate what I mean by making only a small adjustment to the existing construct: if ^f, err := os.Open("readme"); err != nil {
// do something with err
}
// do something with f Strawman specification: For declarations that appear in the header of an I don't really like the non-orthogonality of it being limited only to those three contexts, but I might justify it by noting that these are three situations where the declarations are sitting on a boundary between two scopes: visually, these declarations are neither inside the nested block nor directly in the parent block. In all other contexts a declaration is clearly either inside or outside of each block. I realize this is essentially a competing proposal, but for the moment I'm just mentioning it to see what @markusheukelom thinks about whether this still achieves similar goals despite being a smaller language change. (I also have a feeling that something like this was already proposed before, but I wasn't able to guess what to search for to find it, if so.) |
@apparentlymart Your strawman seems similar to the |
That does seem similar indeed! I think the notable difference is that I intended that only It seems that |
@apparentlymart Yes, this is also an idea to keep the main scope "clean" from
But it seemed that people just didn't like the idea (judging on the downvotes), maybe the syntax is too ugly or maybe there's other problems with it that I don't realise. I do think that the if-statement does become a bit noisy. |
Hah... I guess it was your earlier proposal that I was remembering. 🤦♂️ Sorry for proposing your own idea back to you. FWIW, I prefer the smaller change of just allowing some more flexibility in how these symbols are scoped when using the existing constructs over adding an entirely new construct, especially because the smaller change is also more general and doesn't make any assumptions about error handling in particular. But perhaps I'm in the minority on that viewpoint. 🤷🏻♂️ |
(Just for completeness, that was not my assumption; I did realise that it is mere a convention and not a language feature. Of course,
Yes, this is a drawback of this proposal, but note that in e.g. the range-iterator proposal, functions with special signature are also becoming part of the language specification. Is that significantly different? Note that the syntax could be made non-
Would that make such a proposal more likely to be of interest? I decided to keep the proposal as simple as possible, but I do understand your point. |
Hi @markusheukelom, what if a function returned multiple errors? In my program, I have a function which does
Note: the pathway re-joins after the handling of
I can't write it in It could be helpful if your proposal can cover this use case too. |
I am afraid my proposal cannot (or even should) handle your case elegantly, as the proposal is meant for situations that have a single error variable. I am certainly not saying that your case isn't valid of course, it's just a use case that is a bit rare and for which the current way of returning multiple values can be used. I am not sure it's worthwhile to try an support these cases as for example Go also does not have a There are other error-reporting constructs that cannot be handled by my proposal, for example see my note on custom error types (although an extension can be imagined). If custom error types are to be supported and you are able to change the function signature then of course you could do something like
This would work in the current proposal as well if the function returns |
This is a lot like the recent #65579, with different syntax. |
Based on the discussion, this is a likely decline. Leaving open for four weeks for final commens. |
Yes there is certainly some resemblance. However, this proposal does not introduce a new operator or keyword and is more general in that is also handles map lookup, type assertion, channel receive failure. (There are other proposals using the Understand/agree it is a likely decline. I hope that you've read my "Motivation" section. It outlines an issue with current error handling besides verbosity of syntax which I think is not so explicitly expressed elsewhere (i.e. having |
No change in consensus. |
Proposal Details
In Go, a map lookup, a type assertion and a channel receive operation will report success or failure in a second result value of type
bool
, if requested so for by assignment. This value isfalse
if the operation failed andtrue
if succeeded.Functions and methods report failure in a in similar way by returning a last (and possibly only) value of type
error
. If this value is notnil
, the function failed and the caller should handle the failure appropriately by logging, returning a decorated error, panicking, ignoring, etc. There are exceptions, such asfmt.Errorf
, where returning an error is not a failure, but these are mostly obvious.As such the following constructs are ubiquitous in Go code:
This proposal contains an idea to syntactically handle these kinds of failure reporting using the
else
keyword.This is would works as follows.
Map lookup, type assertion and channel receive operations can optionally be followed by the
else
keyword and a code block. If so, the ensuing code block is executed if the operation failed (ie.ok
in the examples above would be false). When used in an assignment, the assignment (or short variable declaration) takes place before executing theelse
block.Example:
For sake of clarity, the map lookup the example above is identical to following current syntax
and in a similar way for type assertions and channel receive operations.
For function and method calls, if the last return argument of the function is of type
error
, the call can also optionally be followed by theelse
keyword. In this caseelse
must be followed by both an identifier and a code block. The identifier is used to create a variable in the code block capturing the error value which is removed from the call's returned argument list. The else-block is executed if theerror
value is notnil
. The identifier cannot be omitted but is allowed to be_
.This would look as follows:
Note that the
err
variable is lifted to a separate scope (andok
variables are elided completely).If the result of the call is used in an assignment, the assignment (or short variable declaration) of all other values take place before executing the ensuing
else
code block.This is the full proposal. What follows is some motivation for this proposal and a discussion of some open ends.
Motivation
In current Go, variables that are needed to handle failures, often named
err
andok
, are almost always part of the same scope as the result variables. As a consequence, they are visible long after their (intended) usage even though they are no longer needed. A typical symptom of this situation is having to changeerr :=
intoerr =
(or back again) when reorganising code, or having to useerr =
instead oferr :=
becauseerr
was used earlier in the scope somewhere already, handling an error that is no longer relevant.This is resolved using the proposed syntax (if the programmer chooses so), because
ok
variables are now elided completely anderr
variables are now scoped to the block that handles the error. As a result, the "happy path" of the code stays free from the variables that deal with the "therapy path" (failure / error path).Some bugs in Go code appear to be a result of how Go currently deals with error handling syntactically:
It is likely that this type of bug occurs less with the new syntax, because
:=
is no longer used with a mix of existing and new (error) variables:Notice that error handling syntax becomes smoother with the proposal. The overwhelmingly common error handling case of the form
can now be written as
while still allowing handling the error exactly as before (i.e. by means of logging, returning, panicking, ignoring etc). This is a reduction 25% in terms of lines, but also reduces
err != nil
toerr
which saves some additional typing. Of course, the current way of handling errors is still perfectly valid.Finally, using this construct it becomes possible to use functions that return both a value and error
(T, error)
in chain calls and as argument to other calls without using intermediate variables. Of course, readability should always prevail, but this allows "inline" error handling just like it is allowed to e.g. "inline" define a function:Discussion
I do not expect that this proposal is water-tight on arrival. What follows are some open ends. Also, it might turn out that the proposal is completely unusable because I oversaw some syntax ambiguities that arises when using
else
this way. However, it seemed to me that using theelse
keyword to handle failure is worth investigating (maybe even in a different form if this proposal doesn't work) as it does not introduce new keywords and feels somewhat natural to use in this regard.Custom error types
In this proposal, handling errors with
else
the function must return a last argument of typeerror
and not a type that merely satisfieserror
. I chose this for simplicity but the idea could possibly be extended to allow for custom error types requiring only that theerror
interface is satisfied.Custom map types
Likewise, it can be expected that container types will mimic map behaviour by exposing methods such as:
These methods cannot be used with the syntax proposed here, although it could possibly be extended to work with function having a last argument of
bool
instead oferror
as well. However, it is unclear if this does not interfere withif
syntax too much so I left it out.Interference with
if
Finally, it is easy to come up with examples that look a little weird when combined with
if
. For example:Of course, handling errors using
else
is complimentary to current failure handling so if confusion arises it might be better to use the current way of error handling.The text was updated successfully, but these errors were encountered: