-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: Go 2: ErrWrap expressions #39451
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
Please note that you should fill https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing a language change. |
Yes.
C/C++, Java, Javascript, Objective-C, Python, erlang, etc.
Easy to learn. Because we give a more
There are already many error handling proposal. Most of them are care about code size. But I think the important thing is how to find root cause quickly.
It's a basic language feature, and all of Gopher will be helped.
We reinvent error handling specification in Go. I call them ErrWrap expressions: expr! // panic if err
expr? // return if err
expr?:defval // use defval if err All these if val1, val2, ..., valN1 := expr!
// equal to:
val1, val2, ..., valN1, valN := expr
if valN != nil {
panic(errors.NewFrame(valN, ...)) // save error stack frame information and panic
}
val1, val2, ..., valN1 := expr?
// equal to:
val1, val2, ..., valN1, valN := expr
if valN != nil {
_ret_err = errors.NewFrame(valN, ...)) // _ret_err is the last output parameter
return
}
val1 := expr?:defval
// equal to:
val1, val2 := expr
if val2 != nil {
val1 = defval
}
Yes. It doesn't affect existing Go code.
I think
We add new form expressions named
parsing
No extra cost.
Yes. See https://github.com/qiniu/goplus/blob/v0.6.20/exec/golang/stmt.go#L170
No overlap with existing features.
No. But it changes the performance of location root cause of an error. The following is output of the above example I given.
It not only points filename and line, but also what
There are already many error handling proposal. Most of them are care about code size. But I think the important thing is how to find root cause quickly.
No. |
I don‘t feel it does. It seems that the syntactical sugar serves as an excuse to spell out the problem handling even if it is more verbose. Did you consider how your proposal would work with more than 2 return parameters with regards to default values? |
Has some similarities to #32845, #33067, #33074, #33152. I don't understand why it is a good idea to combine the syntax changes with error wrapping. Sometimes it is appropriate to return a wrapped error. Sometimes it is not. Some of this distinction was examined in https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-error-printing.md, although that design draft has not been accepted into the language. |
I didn't. |
I don't think It is similar. In these issues, var ErrInvalidInteger = errors.New("invalid integer")
func atoi(v string) (int, error) {
n, err := strconv.Atoi(v)
if err != nil {
ErrInvalidInteger? // use ErrWrap expression to track error stack
}
return n, nil
}
func add(x, y string) (int, error) {
return atoi(x)? + atoi(y)?, nil
}
func main() {
sum, err := add("10", "abc")
if errors.Is(err, ErrInvalidInteger) {
// do something error handling ...
}
}
|
In this example you're using
I believe the problem is not the need for a shorthand for
But I also note that |
val1 := ErrInvalidInteger
if val1 != nil {
_ret_err = errors.NewFrame(val1, ...) // _ret_err is the last output parameter
return
}
Here ErrInvalidInteger is only to show an error wrapping example. |
Thank you for your reply. If I'm following correctly, you're proposing combining wrapping and control flow changes. This has been discussed in the |
I don't think I combine two things together. Gophers will use their error wrapping themself, as above example. Of course, I also wrap the errors, but it is for error control flow. It is required by error stack tracking. They are different things. |
What is the goal of error handling? I think it is origin of our discussion. |
Since this idea comes from your Go+ programming language, it would be interesting to see large open source projects that have been written in Go+ to compare how easy they are to read versus plain Go code. Do you know of any examples of Go+ projects apart from the compiler itself? |
It have been implemented only for two days. See https://github.com/qiniu/goplus/releases/tag/v0.6.20 |
Personally, I don't find this very clear. Right now error handling is verbose, yes - but it's extremely explicit. I strongly believe that this is one of the reasons that Go software can be so robust. func add(x, y string) (int, error) {
return strconv.Atoi(x)? + strconv.Atoi(y)?, nil
} It's really not obvious what this does. You've described it as "return if err", but you're already in a return statement there? What happens if both error? Where is it returning? Can you add any context to the error yourself? The current situation is verbose here, that's for sure, but it is extremely clear and easy to understand what's happening (i.e. you wouldn't need to know Go to understand what was going on even). func addSafe(x, y string) int {
return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0
} This one is quite interesting, but you can accomplish this already. It'd just mean defining your own function to handle it. As mentioned somewhere above, you'd have problems with functions that return multiple values with a more general approach like this if it were built into the language. I think this kind of thing would be easier to tackle if Go had generics and could implement types like I do really like the idea of automatically adding stack information, but I'd like to see that implemented in a way that means that stack frames were just added automatically any time an error is returned from something; I don't think new syntax would need to be introduced for that. Overall, not really a fan of this, the Go+ README says "Less is exponentially more", I wouldn't disagree in this case, but I'd say the "more" that you're getting is just "more confusion". IMO you do actually get more out of the current error handling because it's so vanilla and easy to grok. |
@seeruk Agree with you |
https://github.com/golang/proposal/blob/master/design/go2draft-error-handling.md Compared to Of course, So, What will happen when we use func add(a, b string) (int, error) {
return check strconv.Atoi(a) + check strconv.Atoi(b), nil
} I think it should be equal to: func add(a, b string) (int, error) {
handle err { return 0, err }
return check strconv.Atoi(a) + check strconv.Atoi(b), nil
} And func add(a, b string) int {
handle err { panic(err) }
return check strconv.Atoi(a) + check strconv.Atoi(b)
} |
No. The frame should be recorded by errors.New or fmt.Errorf. That is the thing to address for Go2. |
If
And then, If we think
|
I’m not sure where your going with check, it’s not a thing. The check / handle proposal mutated into try then was withdrawn. |
I think error frame tracking is required by error control flow, not a user stuff. This is the essence. |
I’m not sure I agree. My thesis is if errors.New and fmt.Errorf captured the stack where they were called then most of the use case for wrapping would not be needed. I see this as independent of control flow syntactic sugar. |
Let's consider error processing style from an example: func foo(...) (..., error) {
if somthing error {
return fmt.Errorf(...) // also for errors.New("...")
}
...
}
..., err := foo(...)
if ??? { // how to check error kind?
// do error processing
} It's difficult to check error kind when we using fmt.Errorf(...). Why? I think It's because fmt.Errorf is a various kind of error frame recording. In fact, I always use the following error processing style: var ErrSomething = errors.New("...")
func foo(...) (..., error) {
if somthing error {
return errors.NewFrame(ErrSomething, ...)
}
...
}
..., err := foo(...)
if errors.Is(err, ErrSomething) {
// or: if errors.Cause(err) == ErrSomething, here errors.Cause is provided by `github.com/pkg/errors`
// do error processing
} That is, So, if we standardize error frame recording and do it automatically, then it becomes the following: var ErrSomething = errors.New("...")
func foo(...) (..., error) {
if somthing error {
return ErrSomething
}
...
}
..., err := foo(...)
if errors.Is(err, ErrSomething) {
// do error processing
} |
Don't forget that |
Thanks. And this proves |
I think it would be neat to have access to compile-time trace values to avoid stack-trace overhead. @xushiwei For example: func add(x, y string) (int, error) {
return strconv.Atoi(x)? + strconv.Atoi(y)?, nil
}
func addSafe(x, y string) int {
return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0
} (with #39372) could be written as: func add(x, y string) (int, error) {
xi, err := strconv.Atoi(x); err.return
yi, err := strconv.Atoi(y); err.return
return xi + yi, nil
}
func addSafe(x, y string) int {
xi, _ := strconv.Atoi(x)
yi, _ := strconv.Atoi(y)
return xi + yi
} Granted, you won't get a trace of the error but as discussed above, that issue can be addressed without syntax changes. |
Why? Errors shouldn't be performance sensitive, the cost of the error is often eclipsed by the cost of recovering from the error; undoing actions, removing temporary files, rolling back transitions, shutting down processes, restarting pods, etc. In that context, the generation of the error itself should be negligible. Addendum, in the Go 1.12 ish timeframe @mpvl suggested that rather than the stack trace leading up to the error, we could capture the program counter at that point; this is extremely cheap, its one word to store and the pc is right there in a register. This isn't the stack trace leading up to the error, but it is the point at which the error occurred. Granted, this isn't as good as capturing the full stack trace, but I argue its much better than no caller information at all and ameliorates the concerns about expensive object construction in the error path. |
The suggested '?' operator is very similar to the
with this proposal one would write
In both cases an expression can cause an immediate return from a function if it returns a non-nil error. One of the main reasons that the The If this proposal has strong support, should we go back to the |
One can implement something similar to @ianlancetaylor's suggestion today in go: |
Based on the discussion above, including the terse syntax and some similarities to the rejected |
No further comments. |
I suggest to reinvent error handling specification in Go. I call them
ErrWrap expressions
:How to use them? Here is an example:
The output of this example is:
Compared to corresponding normal Go code, It is clear and more readable.
And the most interesting thing is, the return error contains the full error stack. When we got an error, it is very easy to position what the root cause is.
How these
ErrWrap expressions
work? I have implemented them in the Go+ language:See Error Handling for more information.
The text was updated successfully, but these errors were encountered: