I see your point. I think it may be better to* narrow the scope* of what 
should be improved.

For the most part, I like the simplicity of error handling in Go, but I 
would very much like a less verbose
way to pass errors.

Technically we are not talking about handling the errors but we just want 
to pass them.


The keyword 'try' may be misleading if our focus is just a simple way to 
pass errors.
I think the keyword we are looking for is `pass`. Similarly, `pass` only 
needs to return if err != nil.

func writeSomething() error {
   f, err := os.Open("file.dat")
   pass err
   defer f.Close()

   f.WriteString("...")
}
---
passing a wrapped error(using the Wrap function above which returns nil if 
err is nil (not a special function) ):


func writeSomething() error {
   f, err := os.Open("file.dat")
   pass errors.Wrap(err, "couldn't open file")
   defer f.Close()

   f.WriteString("...")
}


The following points from your reply still/or should work with `pass` 
(modified):


   -  It is explicit, not hidden
   - The default idiom highlights "error is being passed here" (Go 
   developers learn to look for lines "pass err".)
   - It is possible to set breakpoints (should work similar to a return 
   statement)
   - Code coverage of unit tests reports when the error case has been 
   exercised.
   - Support for Wrapping errors
   - Supports multiple returns with zero values.
   - It is straightforward to parse / analyze.


The following drawbacks are no longer there:

   -  Vertical space: Even in complex cases, the error handling inside the 
   "if" block can be reduced to at most two statements. One line for 
   "handling" the error, and a second line for returning it (or a modified / 
   wrapped version of it). With two additional vertical lines of boilerplate, 
   that's either a 100% or 200% increase in vertical space (the "if" 
   statement, and the closing brace). This limits the amount of code that can 
   fit on my screen, which constrains my ability to comprehend larger 
   functions.
   -  Verbosity: The moment we're know we're checking for errors, the "if 
   err != nil {", the "return", and the "}" are usually just extra syntax.
   - Scanning complexity: What if the check is not err != nil, but err != 
   NotFound? Is this an error case, or an expected case? The current construct 
   hides that distinction. We have to look to the next line to see how it is 
   handled to know that it is an error return.
   - When there are multiple returns on a function, the error handling must 
   specify them all, even though the common case is to return a "zero" value.


The following however is not within the scope for 'pass':


   - If you want to log errors, you're handling it. 'pass' is only for 
   passing errors.
   - If you want to check for a specific error. 'pass' is only for passing 
   errors.
   - If you have a more complicated case.  'pass' is only for passing 
   errors.


pass should work with 100% of the cases within its limited scope. 

I would like to submit a proposal for 'pass', but I'm not sure if it's 
something that would be considered.





On Friday, February 7, 2020 at 5:38:56 PM UTC-7, Eric Johnson wrote:
>
> To make an attempt at articulating why the error handling in Go is 
> valuable as-is, I note the following points:
>
>    - It is explicit, not hidden
>    - The default idiom highlights "error checking being done here" (Go 
>    developers learn to look for lines "if err != nil".)
>    - It is possible to set breakpoints on the error case
>    - Code coverage of unit tests reports when the error case has been 
>    exercised.
>    - It has the full flexibility of identifying the failure scenario, not 
>    limited to err != nil (although that is the overwhelming use)
>    - It has the full power and flexibility of the language for handling 
>    the error - it can be logged, returned, recovered from, sent to a channel, 
>    etc.
>    - If wrapping my error extends to multiple lines (due to longer 
>    messages, number of parameters in the error, etc., that works using 
>    existing language constructs.
>    - It supports arbitrary return statement complexity, including 
>    multiple return values.
>    - It is straightforward to parse / analyze.
>
> That said, it has some drawbacks, particularly in the most common cases.
>
>    - Vertical space: Even in complex cases, the error handling inside the 
>    "if" block can be reduced to at most two statements. One line for 
>    "handling" the error, and a second line for returning it (or a modified / 
>    wrapped version of it). With two additional vertical lines of boilerplate, 
>    that's either a 100% or 200% increase in vertical space (the "if" 
>    statement, and the closing brace). This limits the amount of code that can 
>    fit on my screen, which constrains my ability to comprehend larger 
>    functions.
>    - Verbosity: The moment we're know we're checking for errors, the "if 
>    err != nil {", the "return", and the "}" are usually just extra syntax.
>    - Scanning complexity: What if the check is not err != nil, but err != 
>    NotFound? Is this an error case, or an expected case? The current 
> construct 
>    hides that distinction. We have to look to the next line to see how it is 
>    handled to know that it is an error return.
>    - When there are multiple returns on a function, the error handling 
>    must specify them all, even though the common case is to return a "zero" 
>    value.
>
> For language design purposes, the most common scenario for handling an 
> error is a single-line error return. When it comes to error handling, there 
> are two paths to choosing a new solution:
>
>    - Identify a solution that captures the 80-90% case of if err != nil { 
>    return ... }
>    - Identify a solution that can be used for *all* error handling, where 
>    the existing if err != nil then becomes a legacy code base choice, because 
>    it does not use the new construct which more clearly conveys "error 
>    handling here".
>
> For the benefit of the language, I would prefer a solution that fits the 
> second category, not one that fits the first category. Due to the fact that 
> this proposed solution does not address the arbitrary response complexity, 
> it seems like it fits the first category. If there's a way to tweak the 
> proposal so that it can be used for *every* error handling case, then that 
> would be better.
>
> Go has a long history of implementing orthogonal solutions that happen to 
> combine well. It is possible that error handling is one of those places 
> that could benefit from teasing this question apart a little more.
>
> Eric
>
>
> On Thursday, February 6, 2020 at 7:28:24 PM UTC-8, addi...@gmail.com 
> wrote:
>>
>> Error checking in Go: The try keyword.
>>
>> This isn’t a complete proposal it only describes basic idea and the 
>> changes suggested.
>> The following are modifications that deals with some of the problems 
>> introduced in the original proposal 
>> <https://github.com/golang/proposal/blob/master/design/32437-try-builtin.md>
>> .
>>
>> (I apologize if something very similar that uses a simple method to deal 
>> with adding context has been posted before but I could not find it.)
>>
>> First, try is a keyword not a function builtin.
>>
>> Here’s how error handling with the try keyword works:
>>
>> try err
>>
>> is equivalent to:
>>
>> if err != nil {
>>     return err
>> } 
>>
>> That’s it.
>>
>> Example:
>>
>> f, err := os.Open("file.dat")
>> try err
>> defer f.Close()
>>
>> f.WriteString("...")
>>
>> But, how to add context to errors?
>>
>>  because try only returns if err != nil.
>> You can create a function that returns nil if an error is nil.
>>
>> In fact, a function like this already exists in the errors package here.
>> https://github.com/pkg/errors/blob/master/errors.go
>>
>> // Wrap returns an error annotating err with a stack trace
>> // at the point Wrap is called, and the supplied message.
>> // If err is nil, Wrap returns nil.
>> func Wrap(err error, message string) error {
>>     if err == nil {
>>         return nil
>>     }
>>     err = &withMessage{
>>         cause: err,
>>         msg:   message,
>>     }
>>     return &withStack{
>>         err,
>>         callers(),
>>     }
>> }
>>
>> Example using it with try
>>
>> f, err := os.Open("file.dat")
>> try errors.Wrap(err, "couldn't open file")
>> defer f.Close()
>>
>> f.WriteString("...")
>>
>> That’s it.
>>
>> This should reduce repetitiveness with error handling in most cases.
>>
>> It is very simple to understand, and it feels like Go.
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/5488b745-a9d6-4250-afc7-e50c121e979f%40googlegroups.com.

Reply via email to