-
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: Go2: Functions which when called, can cause the current function to return #35093
Comments
An interesting idea. This would make error handling a bit easier in some cases. If we just reuse existing keywords it could look like this: func break Try(err error) () (error) {
if err != nil {
return break err /* will "break out" of the caller and make it return err. */
}
return continue /* will continue running the caller with no return values. */
} |
@beoran I like it. Using existing reserved words would definitely make backwards compatibility better. What I was thinking was that most of the proposals related to error handling were focused on error handling to the point that they wouldn't be too useful for anything else. A more general approach could be used for other things as well. For example, this could work with errors which are not of type error or even cases were there wasn't an error, but you need to return early in a way that currently results in a lot of repeated code. |
I like the idea. A little more readable could be: func ReturnAValueOrBreakTheCaller(a int32) float32 break int32 {
if a == 10 {
return break 11
}
return 10.0
}
func Foo(a int32) int32 {
b := ReturnAValueOrBreakTheCaller(a)
fmt.Println(b)
return 9
} |
I think the main idea here is to permit a non-local return: a function literal can somehow return from the outermost function (or any outer function?). It might help to see some examples where this would be useful. |
I am not sure it is useful enough I would add it to language. Probably not. But it could have other useful features: // these below are obviously placeholder names
func Parent1() error {
func OpenFileForNow(path string) * File {
file, err := os.Open(path)
if err != nil {
return_parent err
}
defer_parent file.Close()
return file
}
a := OpenFileForNow("a")
b := OpenFileForNow("b")
c := OpenFileForNow("c")
// do things with them
}
func Parent2() int32 {
func Calculate1(x int32) int32 {
if x > 65536 {
break_parent
}
// heavy math
}
func Calculate2(x int32) int32 {
if x < -1 {
break_parent
}
// heavy math
}
x := 0
for {
x1 := Calculate1(x)
x2 := Calculate2(x)
x -= Calculate1(x1 - x2)
x += Calculate2(x2 - x1)
}
return x
} |
Ping @iangudger Can you show some examples where this functionality would be used? |
Here's a case for non-local returns in a closure context... I'm coding a filesystem tree walker using closures which write to a tar.Writer chained to an http.Client.Post() body. It aborts if there's an error writing to the network, and I would like to return the parent function at that point rather than pass an error back up the recursion stack to the parent. |
For the error return case within a single package it's reasonable to call |
I would never panic to handle an expected application state; from what I've read, that's not Good Go. I've seen criticism of stdlib packages for violating that principle. |
There's really nothing wrong with using |
This surprised me, since my understanding was that That being said, if we take this advice and apply it to a concrete example, it does produce a much sought after interface: import "git.sr.ht/~urandom/errors"
func Set(v interface{}) (err error) {
defer errors.Handlef(&err, "setting: %v", v)
_, err := set(v)
errors.Check(err)
return nil
} |
There is a strong connection between the functionality of Thus, the heavy lifting that In other words, Since we already have (For comparison: Smalltalk is a language that actually does support non-local returns. It also supports a method called "unwindProtect:" (or similiar; unfortunately Smalltalk dialects are not standardized). "unwindProtect:" is exactly equivalent to Go's In summary, I don't think we need an alternative feature here. And to confirm what @iant has been stating earlier, it is perfectly ok to use a |
@griesemer, thank you for the thoughtful response. Most of the complaints around error handling in Go have been about the syntax. Explicit error checking with a conditional works fine. There have been a number of seriously considered proposals for changing the language in the name of error handling. All of these proposals that I looked at were adding redundant functionality and the same behavior could be achieved today with more verbose syntax. What bothered me about each was that they seemed to me to be over fit on the most common patterns and lacked flexibility. I tried to address the flexibility issue in this proposal. I personally like the status quo to be honest. I made this proposal under the assumption that the language is going to be changed to add a less verbose syntax for handling errors. |
@iangudger Thanks for providing additional background. Unfortunately, even a nice non-local return mechanism doesn't quite address the error handling problem because - while some of the machinery could be factored out - the factored out code (a local function/closure) would only work for that one enclosing function. As you mention yourself, those couldn't be easily shared/re-used. Furthermore, even in one function a single closure may not suffice due to signature type requirements that may not be uniform. Real macros have even more problems. Macros were a clear non-goal from day one. At some point in the distant past I would preferred a non-local return over a In Smalltalk it works fine because there's only one kind of return - if it happens to be in a closure it's always non-local (and causing the surrounding method to return). Without an explicit return, Smalltalk methods and closures simply return the result of the last expression executed. Such an approach would probably be a poor fit for Go. The various attempts are improving error handling all failed one way or another. For now, the Go Team is not actively looking for another change in this direction - what we have may not be perfect but is has worked for 10 years. If a promising new idea comes up we can always pick it up. Better to err on the side of caution. |
Perhaps, then, since panic, in conjunction with recover, already gives us a way to do non-local returns, we should make that way easier to use and more performant? I'm not sure if it wasn't proposed before, but , for instance, we could allow recover to take a list of interfaces that the panic should implement for it to actually do the recover, if none matches, then it will not recover? That will make some cases of using panic and recover easier for use within a single package. |
Based on the comments above, this proposal doesn't provide functionality significantly different than what we already have in |
There were no further comments. |
defer
is great because it takes one common cause of destructors and splits it out in an easy to use and reason about way. Maybe we could apply this same philosophy to other aspects of the language.One other major missing feature in Go is a macro system. One feature which macros are commonly used for is returning from a function early. This is especially useful for removing boilerplate code for error handling. A mechanism to do just this without the rest of a macro system could retain much of the language's current positive features while allowing substantial removal of boilerplate code.
In order to maintain clear control flow, it would be nice to make it obvious that one was calling such a function.
Possible ideas for implementation:
function
for declaration.go
anddefer
. If this was required, it would make it obvious that the call could result in a return like anif
containing areturn
.break
andcontinue
in nested loops could be used.The text was updated successfully, but these errors were encountered: