Thoughts on the try proposal (and Generics)

133 views
Skip to first unread message

Michal Bohuslávek

unread,
Jul 28, 2020, 6:01:34 PM7/28/20
to golang-nuts
I've been thinking a lot about this Russ's comment 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 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?

Denis Cheremisov

unread,
Jul 28, 2020, 9:40:14 PM7/28/20
to golang-nuts
> 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 is highly questionable assumption. Context outside is obviously superior:
  • Your assumption just does not work. Imagine a generic function like os.Open, it cannot provide really sensible and easy to read context.
  • Context outside means finer control, the annotation can tell exactly what you meant. With try you only can achieve the same with numerous tiny functions of one use and this payload hugely overweights these if err != nil
I really glad this strange proposal was rejected. I was voting for better error handling, but I meant better error handling, not this nonsense. I would like to have something that will not allow to pass error handling by mistake, not the way to save 3 or 2 lines of code in one place just to have additional 6 lines to achieve the same error annotations detais.




среда, 29 июля 2020 г. в 01:01:34 UTC+3, mbohu...@gmail.com:
Reply all
Reply to author
Forward
0 new messages