-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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: try...or
for right-shifting error handling
#52175
Comments
try... or
for right-shifting error handlingtry...or
for right-shifting error handling
Please fill out https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing language changes |
What is permitted to follow |
A single statement or a bracketed code block, with Feel free to help me out. I'm proposing the outline of a solution and need help with the specifics. |
Has the following variation been considered? We could further emphasize the primary code path by dropping the w := os.Create(dst)? or return _err
defer w.Close()
io.Copy(w, r)? or {
// additional cleanup goes here
return _err
} |
w := os.Create(dst)? else return _err
defer w.Close()
io.Copy(w, r)? else {
// additional cleanup goes here
return _err
} Compare with the original code: w, err := os.Create(dst)
if err != nil {
return err
}
defer w.Close()
if _, err := io.Copy(w, r); err != nil {
// additional cleanup goes here
return err
} |
I find the implicitness of
feels clearer for me (as long as try can only address a single statement). It remains to be defined what scope If the additional declaration or
That would need an additional rule of returning zero values for all other return values. |
But it also removes the ability to manipulate the value before returning. Since I like the idea of reusing
I don't like that, either. Maybe an explicit declaration? w := os.Create(dst) else (err error) return fmt.Errorf("create destination: %w", err) If you wanted to reuse an existing variable, you could just do it explicitly still: w, err = os.Create(dst) |
It doesn't. It offers an additional way of expressing things when that manipulation is not needed. I've presented both together, not either or. |
I'd still keep the
that would match with
where a |
You might read this critique of the following approaches:
|
This looks pretty similar to #33029. |
Using a token like |
@ianlancetaylor, Yes, I saw both of those before posting this proposal, but thanks for including the links. It was the popularity of this comment that made me wonder if a slight twist on prior solutions would make people happy. So far, that doesn't seem to be the case. My suspicion is that this community isn't concerned with the issues identified in the original RFP. Maybe a poll would ascertain what error handling issues are worth focusing on, if any. If most select, "error handling is fine as it is," then we needn't put another ounce of effort into it. |
What about |
Eek! That's confusing. I think you've killed the proposal, as written. |
Perhaps the way to save it is to disallow |
Hey @ianlancetaylor. Maybe your team has already considered this, but when you decide what issues to fix according to the polling on this repo, you are only appealing to your current programmer base and not making an effort to expand that base. It is possible to keep the current crowd happy while drawing in others with features they're looking for. For myself, I keep revisiting Go when I need to do heavy lifting, but each time I'm hesitant to commit the next project because I value writing code that can be understood at a glance, without having to mentally filter out clutter. IMO, hard to read code is hard to maintain in the long run and more prone to problems, although Go certainly offsets this in other regards. (And Go devs, I know you disagree with this assessment, but I also know you know Go turns off a good number of programmers. I'm speaking for those who are intrigued but too concerned to commit.) |
@jtlapp Thanks. We try to avoid that by encouraging people who do not currently use Go, but are interested in it, to take our annual survey, where we ask questions like "why do you not use Go more?" See, for example, https://go.dev/blog/survey2020-results. Of course it's impossible to avoid all biasing. All we can do is try our best. And at the same time we recognize that the language is not for everyone. |
This proposal itself may not be it, but I think the use of the else keyword seems very interesting.
This could have the semantics that statement 2 is executed only if statement 1 evaluates to an error, or to an assignment including exactly one error, and so on. file, err := os.Create("foo.bar") else return err
Versus...
if file, err := os.Create("foo.bar") {
return err
} |
@beoran, also this: if _, err := io.Copy(w, r) else {
// additional cleanup goes here
return err
} Instead of either of these: _, err := io.Copy(w, r)
if err != nil {
// additional cleanup goes here
return err
} if _, err := io.Copy(w, r); err != nil {
// additional cleanup goes here
return err
} It does help a little. |
For a while, but eventually you end up with C++. There are very few programming languages that try as hard to keep simplicity as a feature of the language. I'm inclined to say that if you don't value simplicity over features, Go is probably the wrong language for you. |
That's the slippery slope argument, which I think is more dismissive than constructive.
I'm actually trying to remove syntactic features from the primary code path, in order to make the path more visible. Because of the way error handling was implemented, some new feature will be needed. |
That's not what I'm suggesting. Nobody is saying that adding one new error handling feature will inevitably lead to lots of additional complexity. Rather, you suggested yourself that multiple new features could be added to the language to draw in new users without putting off existing Go programmers. I'm skeptical of that as a viable long term strategy. I think the experience of C++ and Perl supports the idea that repeatedly adding new features leads to a more complex language, even if each individual feature has a use case where it makes the code simpler.
No, you're trying to add syntactic features to the primary code path as an available alternative to other syntactic features that you don't like seeing there. But since no feature would be removed from the language, you would be increasing the language's overall complexity. |
You told me that I don't value simplicity, but we can talk about simplicity of the Go specification, and we can talk about simplicity of the code. I'm uncomfortable with Go exactly because it makes the primary code path too complex -- complexity that most Go devs are apparently comfortable with. There is too much boilerplate code obscuring the normally intended operation. Even the original RFP admits this undesirable complexity. |
I've thought of a serious problem with the variants of this proposal. It's confusing to have both of these lines of code valid: w := os.Create(dst) else return _err
w, err := os.Create(dst) We really need a marker up front to tell the dev that we've skipped a return value, as otherwise the dev will have to look ahead to the end of the function call to decide what is being returned. I'll close this issue, as both the original proposal and its variants are problematic. |
We'd need something like this to make the variants work, but this is so different from the original proposal that if there's any interest in it, it probably ought to go in its own thread: w? := os.Create(dst) else return _err
w, err := os.Create(dst) |
@beoran, I suspect your counterproposal is the simplest solution to the problem that this community could possibly accept. I think you ought to propose it in a new thread. If the community rejects it, I doubt there's any change to error handling the community would accept. |
For what it's worth, I think the current way of handling errors is one of the nicest out of any language I've touched, even factoring in its repetitiveness and verbosity. To me, it's not a problem worth solving. |
I do not agree. FWIW, I look at the variables left to the equal sign and know what is returned. Moreover, I think that any proposal that covers the simplest cases is enough as we have always a decent default notation (i.e. the current way) for the more complex cases. Still, in Go the happy path is perpetually interrupted by the unhappy path, plus things like Therefore, I’d like to point the focus on a simplified version of what is proposed above: file := os.Create("foo.bar") else return What this does: But if the last value is nil, all return values except the last one are assigned to the LHS. I see the following nice properties:
|
What about functions that return a |
As I said: “[…], I think that any proposal that covers the simplest cases is enough as we have always a decent default notation (i.e. the current way) for the more complex cases.” My takeaway from the 1000-postings issue of Robert Griesemer’s |
@DeedleFake Your objection clearly shows, however, that my point (10.) should be deleted. If one wants to return something different than the naked Your comment with I will update my main comment above accordingly. |
The problem is when a proposed new notation incentivizes bad coding practices by making them easier. In general, blindly returning error values without wrapping them or handling them is bad code, and in a good codebase it will be done rarely. If you give programmers a way to avoid doing anything with errors by adding That's why in previous discussions, people have repeatedly said that they want to see a proposal that makes good error handling easier, and not just something that makes |
I don’t see any evidence that returning a received error verbatim is considered bad programming. Besides, I don’t see why. There are use cases for wrapping an error, but there are other cases where the original error already says enough. Also note that each language has to be able to scale down, i.e., make simple things simple. Most Go projects are not big, yet their needs deserve being covered by syntax. |
Every function call would wrap its error before returning it? Is this something people are doing to get stack traces? I can't say I've ever heard of this being standard practice in any programming language. Usually people provide distinct wrappings for logical boundaries that are apparent higher in the stack, and these boundaries need not correspond to each function call in the underlying implementation. Otherwise, small changes to the implementation could break callers. I think it's good to look for a solution that invites good error handling practices, but that's only one of the problems I'd like to see addressed. I'd also like to see less error boilerplate so the main code path is clearer and more maintainable. |
No, it's something people are doing so that they don't need stack traces.
No, just most of them. Sometimes returning the error without wrapping is the right thing to do, but that's pretty rare in my experience. The majority of the time you want to provide more context about where the error occurred, what the code was trying to do, and the values that caused the error. |
I just learnt how Zig does error handling: https://ziglang.org/documentation/master/#Error-Return-Traces (BTW, ziplang also uses the mantra “errors are values”.) Here, the Could it be helpful if This would provide enriched and loggable (albeit longish) error values with a succinct “ Disadvantage that comes into my mind: Error values are even more special-cased then (the error wrapping becomes part of the language). |
Here's yet another error handling proposal. I'm warming up to the idea of keeping error handling with the erroring code, but I'm still hoping we can find a way to better emphasize the primary code path.
I noticed that a particular
try
syntax yielded 40 positive emoticons and no negative emoticons, but a far more popular assessment of thetry
syntax pointed out that it was hiding error handling behavior.I thought to employ the
try
syntax without hiding error handling. Under this proposal, the following code:... would take the following form:
If the
or
keyword isn't appealing, maybe someone could suggest an alternative keyword or alternative syntax. Same goes for the proposed auto-assigned_err
identifier, which would hold the last return value.This approach moves all error-handling code to the right, so the primary code path is more visible — less cluttered — along the left. The
try
keyword puts some clutter on the left, but because its syntactically simple and always the same, I'm thinking we would easily gloss over it, especially with 4-space tabs when there is no other return value.UPDATE 1: Any statement or bracketed code block can follow
or
, and_err
would be scoped to that statement or code block.UPDATE 2: Scroll down a few comments to see simpler variations of this syntax that drop the word
try
.The text was updated successfully, but these errors were encountered: