I've been thinking a lot about this Russ's comment 
<https://github.com/golang/go/issues/32437#issuecomment-503297387> from the 
try
proposal:

> But before we get to try, it is worth making sure we're all on the
> same page about appropriate error context. The canonical example
> is os.Open. Quoting the Go blog post “Error handling and Go”:
>
> > It is the error implementation's responsibility to summarize the
> > context.  The error returned by os.Open formats as "open /etc/passwd:
> > permission denied," not just "permission denied."
>
> See also Effective Go's section on Errors.

Specifically, this quote:

> There is lots of code following the Go convention today, but there
> is also lots of code assuming the opposite convention. It's too
> common to see code like:
>
> f, err := os.Open(file)
> if err != nil {
> log.Fatalf("opening %s: %v", file, err)
> }

I'd say, from the comments on the try proposal and other proposals
related to error handling, as well as from various different blog posts
and podcasts, that there's more cases that follow the *opposite*
*convention*, i.e. it is the caller rather than the implementation that is
adding context.

Most functions are called more than once in a program, so adding
context to the implementation itself would benefit every caller: they don't
need to add the context themselves.

This brings me back to the try proposal, which, as far as I know,
was trying to solve the most common problem: removing boiler plate
code when the caller has no additional context to add. In my estimate,
this is roughly 50 % of the cases in a typical codebase. A number
of commenters were arguing that the try proposal doesn't make it
easier to add context to the error, but that wasn't the problem it
was trying to solve. It's already quite easy to add context on the
caller's side: just use if/switch statements or other mechanisms.
(Errors are values.)

So despite being sceptic at first, I ended up being in support of
the try proposal. It solves the biggest issue with error handling,
and it solves it well. Specifically it:

1. encourages the right convention to add context on the callee's side,
2. and makes the code more clear and expressive for roughly 50 % of the 
cases.

In my view, the only valid arguments against this proposal were outlined in 
the
decline comment 
<https://github.com/golang/go/issues/32437#issuecomment-512035919> by 
Robert:

> As far as technical feedback, this discussion has helpfully identified
> some important considerations we missed, most notably the implications
> for adding debugging prints and analyzing code coverage.

As I see it, the first step in the journey to better error handling
is to make most Go code agree on which convention to use: whether
it is the implementation, or the caller who is responsible for adding
context.

--------

Since the recent changes to the Generics proposal make it more
realistic that Go might one day get generics, I was thinking whether
the try proposal could be implemented using them. This is what I
come up with:

// Package eh implements functions for error handling.
package eh

func Check(err error) { … }

func Try[type T](v T, err error) T { … }

func Try3[type T, U](v T, w U, err error) (T, U) { … }

func Catch(errp *error) { … }

func Catchf(errp *error, format string, args ...interface{}) { … }

1. First off, the package name should be short since the package
   would be used a lot in practice.
2. We don't need that many variants for the different argument
   counts: most functions returning errors return 2 values. There might
   be some with 3 values. We might add Try4 or even Try5 if that would
   prove to be useful.
3. The name Check seemed to fit better for error-only checking.
4. In order for this to work the package would need to use panic;
   thus, every function using this kind of error handling would *need*
   to defer calling to either Catch or Catchf. This would probably
   require some sort of vet check to prevent misuse.
5. This package combines variants of the proposed built-in function
   "try" as well as the helper methods as proposed by Russ in
   https://github.com/golang/go/issues/32676. I chose Catch and Catchf
   without further consideration; that's not the point of this post.

Some examples derived from the try proposal:

func CopyFile(src, dst string) (err error) {
defer eh.Catchf(&err, "copy %s %s", src, dst)

        r := eh.Try(os.Open(src))
        defer r.Close()

        w := eh.Try(os.Create(dst))
        defer func() {
                w.Close()
                if err != nil {
                        os.Remove(dst)
                }
        }()

        eh.Try(io.Copy(w, r))
        eh.Check(w.Close())
        return nil
}

func printSum(a, b string) (err error) {
defer eh.Catch(&err)
x := eh.Try(strconv.Atoi(a))
y := eh.Try(strconv.Atoi(b))
fmt.Println("result:", x+y)
return nil
}

There are really 2 questions I want to ask:

1. How to motivate people to use the Go convention (let the
   implementation add the context). Is that the right convention?
2. Do Generics enable implementing a package for try-like error
   handling, or am I missing something?

-- 
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/1f1daaff-9b52-4d89-85e4-35c12de7fa07n%40googlegroups.com.

Reply via email to