-
Notifications
You must be signed in to change notification settings - Fork 11
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
Fix Go's error handling #2
Comments
It's definitely on the roadmap, I'll soon write a longer blog post about future plans. |
I also really like your efforts with Have. Can't await to see it raising 👍 ! What about something like:
So we have either one keyword or a symbol to mark a method as error handler of any kind. In my example it is "!". We can translate this to other use cases like:
Or doing a recover by code block:
Just my brainstormings. Do whatever you like with it. Get inspiration, change it, burn it. Just wanted to take part in this discussion. :-) Keep it up, cheers! |
@christophmegusta thanks! I've just uploaded https://github.com/vrok/have/wiki/Brain-dump-about-contributing, and there's another idea mentioned there. |
|
what about keyword x := try ioutil.ReadAll(r) which would be transpiled to: x, err := ioutil.ReadAll(r)
if err != nil {
return err
} Because we know the signature of the function we can return automatically a 0-value of every return value that is not the error itself func foo() (int, *Foo, error) {
n := try bar()
f := try baz()
return n, f, nil
} which would be: func foo() (int, *Foo, error) {
n, err := bar()
if err != nil {
return 0, nil, err
}
f, err := baz()
if err != nil {
return 0, nil, err
}
return n, f, nil
} The only problem I see with this is that the behaviour is kinda magic but all other error handling with multiple return values that I can think of has at least one corner case where it's flawed. For example, the solution proposed https://github.com/vrok/have/wiki/Brain-dump-about-contributing has a very subtle flaw: watch err {
if err != nil {
return err
}
}
foo, err := foo()
funcThatMutatesErr(&err)
|
@mvader That's interesting, I like it. As for the issue with watchers, I'll quote myself:
(Doing these things would result in a compile error.) |
Oh, then is completely fine. Didn't read that part. With that issue solved
|
BTW. We'll be probably writing the code that checks for variable escaping anyways, to prevent the loop variable scoping issue (the first one here: https://gist.github.com/lavalamp/4bd23295a9f32706a48f). |
I like the |
The problem I see with the try solution is, even though is easier, masks
|
the problem with "try" is it has in our context the wrong semantics. Usually we are used from other languages to ignore and/or catch a error from the "try" keyword. So its the exact opposite we want to achieve. Instead of ignoring a possible error case we want to explicitly leave the current function and return the error to the caller.
|
@christophmegusta rust uses |
I think all proposed error handling mechanisms will be great improvement. Watcher ProposalThe watch proposal is original. I don't think I've ever seen this type of error-handling, but it's very easy to understand and reason about. It's also probably the most flexible approach here, but it has three major drawbacks, in my opinion:
result, err := DoSomething()
anotherResult, err := DoSomethingWithResult(a)
return err It looks like
func returnOnError(err) error {
return err
}
func returnAndWrapOnError(err) error {
if err == nil {
return nil
}
return errors.Wrap(err, "Wrapped error")
}
func returnOnErrorExceptEOF(err) error {
if err == io.EOF {
return nil
}
return err
} The downside of my proposed solution is that it's only useful for error values. You could make the handler function generic, but even then it won't be able to do everything an inline watcher can do. On the other hand, it seems like watchers will be used for handling error values in more than 99% of the cases, so perhaps it's not an issue.
_ := ioutil.ReadAll(r) !"read failed" I can't think of a good solution for that using watchers. Bang Operator Proposal@christophmegusta's proposal has some nice perks:
The drawback is that you can't customize it with other strategies and that's it's tied to a third-party library. Even though There needs to be a way to customize error handling. It's probably feasible to modify this proposal to fix it. Try Keyword Proposal@erizocosmico suggested a If we're going for a language-level feature, I would actually prefer to have a dedicated operator which does the same thing, since error-handling is one of the most common idioms of the language and therefore should be as typist friendly as possible. ;) In any case, the proposed price, err := dbRow.GetIntFieldValue("price")
if err != nil {
return err
}
amount, err := dbRow.GetIntFieldValue("amount")
if err != nil {
return err
}
err := cart.AddTotal(price * amount)
if err != nil {
return err
} to try cart.AddTotal(try dbRow.GetIntFieldValue("price") * try dbRow.GetIntFieldValue("amount")) Or even clearer, following rust and using a dedicated operator: cart.AddTotal(dbRow.GetIntFieldValue("price")? * dbRow.GetIntFieldValue("amount")?)? Rust-style Try-Catch ProposalWe can take the try proposal further by replacing taking the full try-catch proposal from rust RFC 243 which will allows us to customize error handling behavior with catch handlers: try {
dbConn := db.Open("localhost", "3333")?
dbRow := dbConn.GetRowById("OrderItems", orderItemId)
cart.AddTotal(dbRow.GetIntFieldValue("price")? * dbRow.GetIntFieldValue("amount")?)?
} catch(err) {
// Ignore error and don't add items without price or amount to cart
if err != db.FieldNotFoundError {
return errors.Wrap(err, "DB error")
}
} This gives you all the functionality of watchers and allows for more natural expressions (eliding error values). The downside is that we're still going to get some boilerplate, and it still doesn't have a way to customize error messages for It is possible to deal with the boilerplate by the same type of functions I proposed for watchers, allowing you to chain them for added effect: func ignoreError(sentinelErr error) func(error) error {
return func(err error) error {
if err == sentinelErr {
return nil
}
return err
}
}
func wrapAndReturnOnError(err error) error {
if err != nil {
return errors.Wrap(err)
}
}
func addOrderItemToCart(orderItemId string, cart *Cart) {
try {
dbConn := db.Open("localhost", "3333")?
dbRow := dbConn.GetRowById("OrderItems", orderItemId)
cart.AddTotal(dbRow.GetIntFieldValue("price")? * dbRow.GetIntFieldValue("amount")?)?
} catch ignoreError(db.FieldNotFound)
catch wrapAndReturnOnError |
(I'm really glad someone is trying to fix Go, even if it's from a transpiler perspective. E.g.: I really like the fact that you support Generics.)
Now, it would be great if you could fix error handling in Go. It has several disadvantages, such as the default not being safe (ignoring errors), and that it's easy to lose context (stacktrace) when propagating errors with new errors.
There's a good discussion about this here:
https://news.ycombinator.com/item?id=12563887
This talk is also interesting because it highlights the common mistakes of Go devs when using error handling, and how difficult is to do it properly: https://www.youtube.com/watch?v=lsBF58Q-DnY (at the end of the talk, the speaker goes ahead and recommends the usage of an error-utility-library he implemented himself, maybe Have's best approach would just be code generation that makes use of this library? or maybe some kind of try-catch approach...).
Thanks
The text was updated successfully, but these errors were encountered: