-
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: Go2: Drying up Error Handling in Go #36284
Comments
@ianlancetaylor how about this? ^ := handleErr I guess it's also abusing a syntax for purpose it's not supposed to be used |
As the first comment on the corresponding /r/golang post observes, this proposal fails to address the important — and IME overwhelmingly most common — case of uniquely annotating errors in-situ. |
I would assume that How would it handle cases likethese:
these three functions require further analysis, and if it's a syntactic sugar over the current grammar, then it also requires an additional desugar phase. Not too sure if this is a good way forwards. |
Returning 2 errors seems wrong, and is probably very uncommon. I guess you can keep using an If an error is not the last returned argument for some reason (does that ever happen?) it seems that with this proposal, one can put the For the third, I'm hoping that the |
leaving in undefined behaviour is a Bad Idea with capital letters. |
TL;DR : Great criticisms! Check out the examples below for my revision to this proposal. Hey everyone. Great feedback so far here and on the reddit thread. Right now I'm feeling that the core idea still holds promise, but this implementation isn't the right fit for Go. I've been letting it roll around in my head and I'm ready to revise this into some new syntax that addresses the main concerns and more clearly gets at the core of the proposal. The criticisms so far seem to fall into these general buckets: Criticism Categories
Responding to Criticisms
The core idea behind this proposal What seems to bother people the most is the many occurrences of The Revision Lets scrap the single character magic symbol. What we need is a feature to replace The Examples Revised To make this work nicely, we'll need some help from an errors library. The functions of this library will return an // Classic example
func getId() (error, int) {
// ...
val, err := returnsTypicalError()
if err != nil {
return errlib.Wrap("Custom err text here", err), 0
}
// ...
}
// Converted to use `respond` syntactic sugar
// 0 is not nil. It satisfies the "all arguments are not nil" condition.
// `respond` only forces the return when all of its arguments are non-nil
func getId() (error, int) {
// ...
val, err := returnsTypicalError()
respond(errlib.Wrap("Custom err text here", err), 0)
// ...
} // Because we're not handling errors inline, we can deal with non-conventional
// return patterns
func getId() (error, int) {
// ...
a, err, b := returnsErrorInMiddle()
respond(errlib.Wrap("Custom err text here", err), -1)
// ...
} // The classic pattern handles multiple errors quite nicely. We can use Go's
// if-scoped variable syntax to combine errors and respond cleanly.
func getId() (error, int) {
//...
err1, err2 := returnsTwoErrors()
if err := errlib.CombineErrors(err1, err2); err != nil {
return errlib.Wrap("Custom err text here", err), 0
}
//...
}
// In the `respond` equivalent, nesting our lib functions would be too verbose
// and difficult to read. We'll have to break the code into two lines just the way
// we would with normal Go code. If our final err is not nil, we'll get an early return.
// If we end up passing a nil error around, we continue beyond `respond`.
func getId() (error, int) {
// ..
err1, err2 := returnsTwoErrors()
err := errlib.CombineErrors(err1, err2)
respond(errlib.Wrap("Custom err text here", err), 0)
// ..
} // This is a compile time error because the types given to `respond` do not match up
// positionally with the types returned by the encapsulating function
func getId() (error, int) {
// ..
v, err := -1, errors.New("test error")
respond(v, err)
// ..
} Sorry that got way longer than I anticipated. But what do you guys think? Anything interesting there? |
The revised proposal seems to have a lot in common with #32437. You may want to glance over the various issues, both open and closed, with the label error-handling. |
@ianlancetaylor Well... yeah. This proposal and the One criticism of func getId() (int, error) {
// ...
v, err := returnsTypicalErr()
respond -1, err
// ...
return id, nil
} I'll continue to read more about |
Hi, we're trying out a new process for language changes. Can 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! |
Thanks. I think that |
Introduction
We are still experimenting with possible ways to simplify error handling in future releases of Go. I believe that most frustration comes from the repeating of simple error cases, and that we can introduce a simple syntax to help remove this tedious code. The new syntax is inspired by existing Go conventions, will not break existing code, and should feel familiar to the community.
The Proposal
I see there are 4 main cases of errors in Go that we handle as developers.
Case 1
We already have special syntax for case 1. There are no changes here.
Case 2
Inspired by the
_
operator, I propose a similar syntax for returning errors if they exist.Case 3
For case 3, it is important that we allow developers to wrap their errors. In many cases, the wrapper or error handler is simple enough to factor into an external function. We must be mindful of which handler is being used for the error, but we still want to clean up the
if
statement that hangs out below the source of the error. For this case, I'm proposing we use existing channel syntax to register an error handler with the^
operator, and return the error through the handler.This pattern is inspired by how we typically remove
else
statements from our code by beginning with theelse
case and checking theif
case afterward :As you can see above, registering a handler will persist until a new handler is registered. This is necessary to eliminate repeating ourselves if we have a logger that is used for most errors.
Case 4
This final case is our catch all. It allows developers to gracefully handle their errors and provides backward compatibility with the existing convention. There are no changes here.
Dependency Injection
One issue that is immediately obvious is the lack of additional arguments in the error handler. To inject additional dependencies, we would have to produce the handler as the result of a closure or use a package such as the popular
wire
package. I think that using a Handler constructor for dependency injection would be elegant enough to inline as in the followingRestricting the Operator to Errors
Because errors are just values, there's really no reason that this new
^
operator couldn't be used for types that are not errors. It is a "Return val if exists" operator and it accepts a single function on a channel that acts as the error handler.It's not clear to me that using this operator for any value other than errors would be beneficial in the way that using
_
is. I recognize that it could be easy to miss in verbose code, and for this reason, I would recommend it be restricted to types that implement the Error interface. In restricting the operator to only error types, we would also need to restrict the handler func to a handler interface which would be the following :Multiple Return Values (Added in edit)
If a non error return value has not been initialized, it would be returned as the default "zero" value as is the current convention. If it has been initialized, it would be returned with its current value which is the case for named return values.
Conclusion
I'm certainly not an expert in the field of language design, so I welcome all comments and criticisms. My favorite feature of Go is its simplicity, and I hope I can help move the conversation around error handling to be about "What is the smallest change we need to solve this problem?"
I believe that the frustration around error handling in Go is around the verbosity of the
if err != nil
check, and therefore the smallest change we must make is one that eliminates as many of theseif
checks as possible while still allowing for easy to read, easy to write, and easy to debug code. We don't need to eliminate them all. We just need enough to make Go a little bit more fun to write in.Thanks for reading!
The text was updated successfully, but these errors were encountered: