-
Notifications
You must be signed in to change notification settings - Fork 18.1k
proposal: Go 2: error handling with error receiver function #36338
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
Comments
If I write code like this func F(b bool) error {
errorReceiver := func(err error) error { return err }
if b {
f, errorReceiver := os.Open("file")
// Do something with f
}
} I don't understand how we can reliably and simply decide that the assignment to |
@ianlancetaylor that is an excellent point, I was really trying to avoid it but I guess the only way for this to work is to have a syntax change that would explicitly state that
I like the last one a lot personally, so your example would become: func F(b bool) error {
errorReceiver := func(err error) error { return err }
if b {
f := os.Open("file"):errorReceiver
// Do something with f
}
} Which would resolve the ambiguity of my initial proposal. |
P.S. now that I think about it this func F(b bool) error {
errorReceiver := func(err error) error { return err }
if b {
f := os.Open("file"):errorReceiver
// Do something with f
}
} is just syntactic sugar for func F(b bool) error {
errorReceiver := func(err error) error { return err }
if b {
f, err := os.Open("file")
if err != nil {
if err2 := errorReceiver(err); err2 != nil {
return err2
}
}
// Do something with f
}
} |
I looks like using func key() (string, error) {
return "key", nil
}
func F(b bool) error {
errorReceiver := func(err error) error { return err }
if b {
m := map[string]bool{
key():errorReceiver: true,
}
}
} |
I don't think symbol should be the focus here as we can easily use something else, in fact I don't think I like 1 character symbol at all, since this affects the flow of control it should probably be a keyword:
But more importantly are there any fundamental flaws with this approach that I'm not seeing, would this solve majority of issues that people have with the current error handling and most importantly is this go like. These are the questions I don't have answers to. |
Well then this isn't too different from func F(b bool) error {
errorReceiver := func(err error) error { return err }
f := try(os.Open("file"), errorReceiver)
// Do something with f
} |
TLDR; You should not expect any error handling proposal to be drastically different from I was not aware To clarify what I mean, the problem we are all trying to address (as far as I can tell) is highly repetitive error handling, the solution to repetitive code is well known simply factor out code into reusable functions. We should be able to apply the same solution to error handling, I do not see any reason to reinvent the wheel here. This proposal, If you think about it, this solution, |
It wasn't. It appeared in a few comments, but it was never part of the proposal itself. Which is a problem, since one of the biggest criticisms was that you couldn't easily add context to the error (you'd have to use named returns and defers, which added quite a bit of verbosity). Your proposal, to me, seems to cover 2 of the biggest criticisms of the original With this one, you can easily add context to an error. You can also have helper functions in the stdlib that can make it even easier (e.g.: Unlike |
I know that you are not insisting on a particular syntax, but the syntax does matter. One problem with the Also, it will be very cryptic to a person new to Go. At least So I think this needs more attention to syntax. |
FYI, here's the discussion of the |
Since this is a language change proposal, could you please fill out the template at https://go.googlesource.com/proposal/+/bd3ac287ccbebb2d12a386f1f1447876dd74b54d/go2-language-changes.md . When you are done, please reply to the issue with Thanks! |
How about an alternate syntax:
I'd also prefer an inline variant:
|
"One problem with the "Also, it will be very cryptic to a person new to Go" - Also agree. As I was saying My original proposal intended the
The meaning of word Perhaps something like the below is more logically sound: errorReceiver := func(err error) error { return err }
f, err := os.Open("file") or errorReceiver
handle err or more concisely: errorReceiver := func(err error) error { return err }
f := handle (os.Open("file") or errorReceiver)
Just wanted to give an update as I was away thinking about this for a couple of weeks. |
@guilt I kind of agree with others that symbols are not the way to "go" (... I'll walk myself out). |
@drupsys I personally don't think that the function handler passed to the operator should return any other type other than the one passed in. It should always look like (in a generic syntax): I would agree that if the handler returns nil, the control should not be terminated. Though any other assignment (in your example the *File) should be zeroed, and the user should then have to initialize that variable in a separate control flow ( |
@drupsys - For me it's less about symbols but more about making error handling concise to relay, where possible. I'm also all for having it explicitly modeled as a variable (err) than an invisible variable (throw) |
+1 to the concept in this proposal. One of the biggest improvements over the check/handle proposal is that I can specify which handle I want as opposed to using the closest in scope handle func. That brings in much needed flexibility, especially if I want to alternate between handlers. I also agree that it'd be beneficial to focus more on the concept, and later on the syntax. Trying to do both simultaneously is likely to be unproductive. If we can agree that this approach is flexible and robust enough, then we can bike-shed on the syntax (just kidding, I know syntax is important) . Since I"m in agreement with this approach, I'll go ahead and propose a syntax as well :) func errorHandler(src, dst string) (func(error) error) {
return func(err error) error {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
}
func CopyFile(src, dst string) (err error) {
r := check os.Open(src) with errorHandler(src, dst)
defer r.Close()
w := check os.Create(dst) with errorHandler(src, dst)
defer func() {
w.Close()
if err != nil {
os.Remove(dst) // only if a “try” fails
}
}()
check io.Copy(w, r) // not specifying a handler defaults to siply return the err
err := w.Close()
check err with errorHandler(src, dst) // you can also check errors directly
return nil
} check/with can easily be switched with other keywords. I will mention I do prefer not leveraging a function like try, since parens add more clutter IMO |
I would like you to consider this suggestion. It basically adds a catch syntax in which the first func CopyFile(src, dst string) error {
r := os.Open(src) catch
defer r.Close()
w := os.Create(dst) catch
io.Copy(w, r) catch { w.Close(); os.Remove(dst) }
w.Close() catch { os.Remove(dst) }
return nil
catch (err error):
return fmt.Errorf("copy %s %s: %v", src, dst, err)
} |
Timed out in state WaitingForInfo. Closing. (I am just a bot, though. Please speak up if this is a mistake or you have the requested information.) |
Uh oh!
There was an error while loading. Please reload this page.
Hello everyone,
What if a function that receives only an error could be used as an error receiver for a failed function call?
OK this may be completely crazy also maybe someone already suggested something similar but hear me out...
Overview
Assume that
os.Open
fails with an error, since the signature oferrorReceiver
takes an error as argument theerrorReceiver
could be called with theos.Open
error. Lets see what that would do to the order of execution.r := os.Open(src) or errorReceiver
os.Open(src)
returnsnil, fmt.Error("Some error")
errorReceiver(fmt.Error("Some error"))
errorReceiver
is still an error then assignerrorReceiver
's error toexample
's return error and exit fromexample
errorReceiver
is nil then continue execution ofexample
With only one new language concept we are able to reusable handle errors e.g.
If in addition to this go was also able to infer the return type of higher order functions then you could end up with pretty clean syntax for error handlers. The above error handler rewritten would look something like.
Although even without it, this would be a significant improvement.
Handling errors and recovering from them
What if you actually want to handle the error not just simply enrich it? A more sophisticated implementation of error receiver could be implemented. So far all examples of error receiver returned an error, lets go back to my initial example.
What if error receiver here return just the
*File
, which is the first return value ofos.Open
, e.g.Since
errorReceiver
is no longer returning an error but the expected value ofos.Open
then the result oferrorReceiver
could just be assigned tor
and the execution of theexample
could continue, this effectively handles the error.Note that if
os.Open
returned more than one non error value e.g.func Open() (T1, T2, ...Tn, error)
then error receiver would have to have the following signaturefunc(error) (T1, T2, ...Tn)
More concretely error receiver would have the following valid signatures:
func(error) error
- Error receiver that always results in a failurefunc(error) T1, T2, ...Tn
- Error receiver that always results in recoveryfunc(error) T1, T2, ...Tn, error
- Error receiver that may recover or failFinal example to show a case where an error receiver itself might recover or fail
Conclusion
I think this is a rather neat approach since the only new thing added here is, admittedly, a slightly bizarre behaviour of error receiver when it returns, although the official
try(...)
error handling proposal was effectively doing the same thing, everything else here is just basic functions. Or am I just having brainfarts? Anyway, I hope someone will find this interesting.Thanks.
The text was updated successfully, but these errors were encountered: