Go 2 error handling non-proposal

563 views
Skip to first unread message

DrGo

unread,
Oct 29, 2018, 10:26:39 PM10/29/18
to golang-nuts
What I like about error handling in Go is that errors are just values. Like any other values, they can be transparently stored, aggregated, passed around and used, among other things, to explicitly change program flow. That is why I am on the whole satisfied by Go 1's approach and a bit concerned about many of the Go 2 proposals that provide specialized syntax for handling errors.

I know that checking for errors can lead to repetitive and verbose code. My solution has been to refactor repetitive error handling logic into a package-level function or a method on a receiver (or even a closure within a method or function) depending on needs. For example in a parser package, I might have a single method of the parser object that aggregates all parsing errors and then provide an Err() method that can be used by caller to check for and report any errors. This is the same approach used in several occasions in the standard library and often results in clean and idiomatic interfaces.

This approach does not work as well in some scenarios, e.g., in routines reading and writing files, where there are different types of errors potentially returned at every step of opening, reading, creating, writing and closing files and often requiring bailing out from the routine hopefully with an informative error message. This often results in a code like this:

func CopyFile(src, dst string) error {
 r
, err := os.Open(src)
 
if err != nil {
 
return fmt.Errorf("copy %s %s: %v", src, dst, err)
 
}
 defer r
.Close()


 w
, err := os.Create(dst)
 
if err != nil {
 
return fmt.Errorf("copy %s %s: %v", src, dst, err)
 
}


 
if _, err := io.Copy(w, r); err != nil {
 w
.Close()
 os
.Remove(dst)
 
return fmt.Errorf("copy %s %s: %v", src, dst, err)
 
}


 
if err := w.Close(); err != nil {
 os
.Remove(dst)
 
return fmt.Errorf("copy %s %s: %v", src, dst, err)
 
}
 
return nil
}



My approach currently is to try and reduce the clutter and repetitiveness by creating one or more closures having the same return signature as the enclosing function, so that I can return it if error is not null. That results in the following code which for me represents a reasonable trade off between avoiding repetitiveness and readability. 



func
CopyFile(dst, src string) error {
 report
:= func(err error) error {
   
return fmt.Errorf("failed to copy %s to %s: %v", src, dst, err)
 
}
 closeAndReport
:= func(w io.WriteCloser, err error) error {
    w
.Close()    //works because closing an already closed file is ok
    os
.Remove(dst)
   
return report(err)
 
}
 r
, err := os.Open(src)
 
if err != nil {
 
return report(err)
 
}
 defer r
.Close()


 w
, err := os.Create(dst)
 
if err != nil {
 
return report(err)
 
}


 
if _, err := io.Copy(w, r); err != nil {
 
return closeAndReport(w, err)
 
}


 
if err := w.Close(); err != nil {
 
return closeAndReport(w, err)
 
}
 
return nil
}


For me, the only needed change here is perhaps a mechanism to simplify the if err!= nil { return myClosure()} bit. I guess this is what the proposed "check" keyword in Russ's proposal is supposed to do. For example, 

r, err := os.Open(src)
if err != nil {
 
return report(err)
}



becomes

r := check os.Open(src)


As many have pointed out, the check keyword has several shortcomings the most important for me are that:
(1) it obscures the change in workflow and (2) it treats error values differently than all other values.

Overall, I do not think that the gain in brevity compensates for the loss in code clarity (and for the addition of 2 new keywords). Perhaps I am lacking in imagination, but I am finding it really hard to think of ways to beat the exquisite balancing act that Go 1 achieves when it comes to handling errors and exceptions. 

What do  you think?

Burak Serdar

unread,
Oct 30, 2018, 10:01:42 AM10/30/18
to salah....@gmail.com, golang-nuts
On Mon, Oct 29, 2018 at 8:26 PM DrGo <salah....@gmail.com> wrote:
>

>
> As many have pointed out, the check keyword has several shortcomings the most important for me are that:
> (1) it obscures the change in workflow and (2) it treats error values differently than all other values.
>
> Overall, I do not think that the gain in brevity compensates for the loss in code clarity (and for the addition of 2 new keywords). Perhaps I am lacking in imagination, but I am finding it really hard to think of ways to beat the exquisite balancing act that Go 1 achieves when it comes to handling errors and exceptions.
>
> What do you think?

I agree that the brevity suggested by the official proposal obscures
the code. There are some counter-proposals that suggest similar ideas:

https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback


>
> --
> 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...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Liam

unread,
Oct 30, 2018, 4:26:35 PM10/30/18
to golang-nuts
Hi, could you link this thread on the Feedback wiki page (probably in the Against section)?

URL to this thread:

It seems clear that the Go team has resolved to add *something* to the language for error handling; they've heard a lot of complaints, over a long period. Also there's much more to error handling than what's contemplated in the draft design; see the Requirements section of the wiki.
Reply all
Reply to author
Forward
0 new messages