-
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: use symbol or function call in assignment for error handling #33150
Comments
Has some similarities to #32884. |
I'm not fond of the way that the more complex variants put the error handling (the call to |
I can understand that. Two function calls with orthogonal purposes on the same line is going to quickly feel like clutter, particularly if the error handling side is longer, and anything that makes the actual work less obvious is a no go. So if we exclude error handling on the same line, maybe all that's left is a handle keyword? f, err := os.Open(filename) if err != nil { return err } f, err := os.Open(filename) handle err return err f, err := os.Open(filename) handle err return decorate(err) f, err := os.Open(filename) handle err { return decorate(err) } Reduces boilerplate, keeps returns explicit but not a significant improvement over a more relaxed format for if statements, for which I think there must already be another proposal somewhere. f, err := os.Open(filename) if err != nil { return err } |
|
I like that this proposal restricts the life of the I dislike that it makes error decoration more complex and forces an implicit |
And a mix of both ! and "handle" (changed to catch, as it feels fitting) would give us: ! denotes that the error is returned, the "catch" allows to name the error variable and control flow for cases that require more than a simple return. f, ! := os.Open(filename) f, ! := os.Open(filename) catch err { return decorate(err) } f, ! := os.Open(filename) catch err { log(err) cleanup() } And as it's not an if, gofmt could allow: f, ! := os.Open(filename) catch err { return decorate(err) } or even f, ! := os.Open(filename) catch err return decorate(err) Restricts the scope of the error and reads easily enough but it's both a new key symbol and a new key word, so from what I gather, not popular. The catch would not have to directly follow the !, but scope would then get interesting. |
Gofmt could enforce the following if the catch can only ever intercept that one error and is a single line. This is all about minimizing vertical space for what might be considered "unimportant" compared to the actual work. f, ! := os.Open(filename) catch err { return decorate(err) } Catching multiple errors, or multiline catch, could look like: func MyFunc() (error) { ... f, ! := os.Open(filename) defer f.Close() g, ! := os.Open(otherfilename) ... catch err { ... } }
In my opinion, enough proposals around to indicate that using a key symbol is intuitive enough when reducing boiler plate. |
@ctlod I am weary of multiline catches. It means that decoration is not specific to a function rather than an error. It also stops the ability to have different flows for different errors. Most errors I decorate and |
The
The |
@wdc-python-king I really missed the obvious there, and assignment is worse than declaration. r, !! = os.Open(filename) r, !! := os.Open(filename) @mvndaai I prefer a multiline catch to an implicit return. var ( id int name string ) rows, err := db.Query("select id, name from users where id = ?", 1) if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { err := rows.Scan(&id, &name) if err != nil { log.Fatal(err) } log.Println(id, name) } err = rows.Err() if err != nil { log.Fatal(err) } VS var ( id int name string ) rows, !! := db.Query("select id, name from users where id = ?", 1) defer rows.Close() for rows.Next() { !! := rows.Scan(&id, &name) log.Println(id, name) } !! = rows.Err() catch err { log.Fatal(err) } VS var ( id int name string ) rows, !! := db.Query("select id, name from users where id = ?", 1) catch err { log.Fatal(err) } defer rows.Close() for rows.Next() { !! := rows.Scan(&id, &name) catch err { log.Fatal(err) } log.Println(id, name) } !! = rows.Err() catch err { log.Fatal(err) } Both would acceptable and have less baggage than the original, but my preference is for the multi-line catch as I feel that it detracts less from the intended purpose of the code. |
@ctlod Wow, these symbols (
OR
|
@wdc-python-king The first brings nothing new over the "if err != nil" construct, as for the second, I personally like having a clear indication of where an error might be thrown or else I can't tell if an unfamilier function even has the possibility of returning an error. The rules I'm using to evaluate my suggestion are as follows: (And they have changed since I first wrote the proposal)
For the symbols, I feel the learning curve is quite shallow. How long did it take to be familiar with '_' ? After using it a couple of times, do you ever find yourself going back to the specs to lookup what it does? Out of curiosity, Does '!_' bother you as much as '!!' ? f, !_ := os.Open(filename) |
@ctlod Yes, it does. The My first example could obey some of your rules.
I have a idea. Since ignoring an error is a bad practice, how about this
instead of
Will a bad practice become a good one? |
@wdc-python-king I'm not a fan of changing the meaning of existing language constructs. |
Use !_ (Not Irrelevant) to denote an error that prematurely ends the current function if not nil. In a nutshell: var ( id int name string ) rows, err := db.Query("select id, name from users where id = ?", 1) if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { err := rows.Scan(&id, &name) if err != nil { log.Fatal(err) } log.Println(id, name) } err = rows.Err() if err != nil { log.Fatal(err) } becomes: var ( id int name string ) rows, !_ := db.Query("select id, name from users where id = ?", 1) defer rows.Close() for rows.Next() { !_ := rows.Scan(&id, &name) log.Println(id, name) } !_ = rows.Err() catch err { log.Fatal(err) } The catch statement declares the variable available within the scope of the catch block, and the blank identifier is legal. catch _ { ... } The type can be determined at compile time, using type error if multiple types. To reduce vertical space consumption, a one line variant could be allowed by gofmt in cases where it only catches the preceeding line. rows, !_ := db.Query("select id, name from users where id = ?", 1) catch err { return err } defer rows.Close() Some restrictions:
For errors that do not prematurely end the current function, I'd argue that they continue to be treated just as variables and use the if err != nil approach. |
Currently What if we have func F() (error, error) { return errors.New("1"), errors.New("2") }
func G() {
!_, !_ = F()
catch err {
// What is the value of err here?
}
} This doesn't improve the case where different function calls should handle errors in different ways. This proposal seems to shift for each comment that people make, which makes it harder to evaluate. It might be better to discuss this elsewhere and try to produce a new issue with a more polished version. For these reasons this particular proposal is a likely decline. Leaving open for four weeks for final comments. |
I would argue that My understanding is that some people (myself included) would appreciate a more concise way to simply exit a function when they get an error, with the option for limited enhancing/logging/cleanup. For a function that returns 2 error variables, where both are flagged as "return on error", a simple left to right precedence would suffice. I personally don't have an issue with allowing only 1 For function calls where errors need to be handled properly (instead of a quick exit) then the existing constructs are more appropriate, and for functions where errors imply a quick exit, but the limited enhancing/logging/cleanup differs, I personally don't see any way of making that more concise. Sorry for the evolving proposal, was not sure on the usual practice. |
Thanks for the additional comment. A concise way to return when an error occurs might be nice, but this doesn't seem to be the best way to do it in Go. |
The final form of this proposal can be found further down in #33150 (comment)
Use ! (or some other symbol) as special symbol in assignment for error handling
My issues with a built-in function try as proposed is 4 fold.
can be simplified to
This resembles the _ key symbol, but is only applicable to errors.
HandlerFunctions: func myHandler([...]) error
Takes at least 1 argument compatible with the type of error returned.
eg:
And used as so:
this is equivalent to:
Although, in this simple case, it would suffice to do:
With support for error with return values:
I'd expect most function calls using ! to remain rather compact.
Special case, but probably marginal.
And of course for the cases where an error doesn't imply an immediate return, then the existing "if err != nil" remains appropriate, as flow is continuing after the function call statement, and the error is treated as nothing more than a variable.
Thanks for your time parsing this late and naive suggestion.
The text was updated successfully, but these errors were encountered: