-
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: spec: new builtin: handle #69045
Comments
CC @jba for experience with doing this explicitly in pkgsite code. |
I use the explicit function in all my code now. I use this function:
and call it like this:
I don't find that to be a signficant burden, because I only use it for "important" functions. That seems to be about 10% of all functions, based on the pkgsite code (actual numbers: 2,292 functions, 263 calls to Wrap). And maybe a quarter of those functions already need named return types for documentation. I also very rarely need to transform an error, just to add some context to get an approximate call trace. So I don't think adding a built-in function is worth it. |
@jba your example could be added to defer handle(fmt.Wrapf("Frob(ctx, %d)", n)) That's probably what 80%-90% of the uses of The next most common case would probably be logging like: defer handle(logError) Note that this is reacting to the error not transforming it but it can still be written as a transformer: func logError(err error) error {
doTheLogging(err) // reaction
return err // identity transform
} As errors are just values so are error transformers just functions. We could just add functions to One thing I've run into a lot of times is that I'm working with a lot of io and I want to return on an error but I want to return defer handle(func(err error) error {
if err == io.EOF {
return nil
}
return err
}) and even shorter with #21498: defer handle(err => {
if err == io.EOF {
return nil
}
return err
}) I've written that using func Real() error {
err := realImpl()
if err == io.EOF {
err = nil
}
return err
} because it's less bother than dealing with all the stuff This goes for anything you'd want to do the same thing in multiple error paths but that I've experienced most often in this one case. It's true that |
I think that a more generic It could be used to implement handle(): func Handle(transformer func (error) error){
retVals := returned()
retErr := **(retVals[len(retVals) - 1]).(error)
if retErr == nil {
return
}
newErr := transformer(retErr)
*retVals[len(retVals) - 1] = &newErr
} |
That would require many changes to the language before you could represent and use the result. You could narrow that to something that returns a |
I've modified my original comment. What other changes to the language will be needed compared to just returning |
You would need to define what makes all of those lines of code legal. For example, you cannot have a slice where each value is a different type. That is a very complicated thing and not on topic for this issue. |
Thanks for the helpful proposal. This is an idiom that some code uses but it is not particularly common. As both @jba and the original proposal point out, we can already do this, albeit more verbosely than this proposal. The emoji voting is not strongly in favor. Since it's not common and there is a way to do it, the benefit of this isn't worth the cost of a language change. Therefore, this is a likely decline. Leaving open for four weeks for further comments. |
What
handle
must be deferred and only in a function whose last return value is typeerror
. For brevity, I'll refer to this value as "the error" of the function.handle
takes an argument of type assignable tofunc(error) error
. For brevity, I'll refer to this argument as an "error transformer".If the error is
nil
, the error transformer is not called. If the error is notnil
, the error transformer is called and its result replaces the error. Essentially:Why?
This allows error handling routines to be written as library code. For example
which can be used like
How?
This can be done via a macro expansion like mechanism in the compiler.
Given
the compiler can treat it as if the code was
Alternatives
This could just be a regular function like:
That has two downsides:
A builtin obviates these, making it easier to use and thus more likely to be used.
This could be a keyword like
which is obviously much better but not backwards compatible.
A
func returnedError() *error
builtin that returns a pointer to the error of the deferring function. This is more powerful. You could use it to writehandle
and other things. It's a bit too magical and harder to use.The text was updated successfully, but these errors were encountered: