Repository: go
Issue: proposal: spec: the #id/catch error model, a rethink of check/handle
Labels: LanguageChange,Proposal,error-handling,LanguageChangeReview

_Please __do not down-vote__ this post if you are against any new syntax for error handling.  
Instead, vote in the first comment below. Thanks!_

Having heard users' frustrations with Go1 error handling, the Go team has committed to delivering a new method. Ideally, a solution would stem from a familiar language. The [_Go2 Draft Design_](https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling.md) is fine for wrapping errors with context, and returning them succinctly. But its feel is novel, and it has significant drawbacks, discussed in [_Golang, how dare you handle my checks!_](https://medium.com/@mnmnotmail/golang-how-dare-you-handle-my-checks-d5485f991289)

Besides returning errors, Go programs commonly:
a) handle an error and continue the function that received it, and  
b) have two or more kinds of recurring error handling in a single function, such as:
```
{ log.Println(err) }
{ debug.PrintStack(); log.Fatal(err) }
{ if err == io.EOF { break } }
{ conn.Write([]byte("oops: " + err.Error())) } // e.g. a network message processor
```

There is indeed a long list of [_Requirements to Consider for Go2 Error Handling_](https://gist.github.com/networkimprov/961c9caa2631ad3b95413f7d44a2c98a). The `check/handle` scheme accommodates a tiny subset of these, necessitating an awkward mix of Go1 & Go2 idioms, e.g.
```
handle err { return fmt.Errorf(..., err) }
v, err := f()
if err != nil {
   if isBad(err) {
      check err     // means 'throw'
   }
   // recover
}
```
Herein is a widely applicable approach to error handling, leveraging the C-family catch block. For the record, the author is grateful that Go does not provide C++ style exceptions, and this is not a plot to sneak them into the language through a side door :-)

### The `#id/catch` Error Model

Let a _catch identifier_ (catch-id) e.g. `#err` select a named handler. A single catch-id may appear in any assignment. A handler is known by its parameter name; the parameter can be of any type. A handler follows the catch-id(s) that trigger it and starts with `catch <parameter>`. Catch-ids are not variables and handler parameters are only visible within handlers, so there's no re-declaration of error variables.

These are not unique ideas. At last count, [17 posts on the feedback wiki](#named-handlers-are-popular) suggest various ways to define and invoke named handlers, and 13 posts suggest invocation of handlers using assignment syntax.

```
func (db *Db) GetX(data []byte) (int, error) {
   n, #return := db.name()                       // return our own errors
   
   f, #err := os.Open(n)                         // this file's presence is optional
   defer f.Close()
   _, #err  = f.Seek(42, io.SeekStart)
   l, #err := f.Read(data)
   
   #return = db.process(data)
   
   catch err error {                             // handle OS errors here
      if !os.IsNotExist(err) { log.Fatal(err) }
      log.Println(n, "not found; proceding")
   }

   #return = db.index(data)                      // executes unless catch exits
   return l, nil
}
```

Several points are unresolved, see [Open Questions](#open-questions) below. Catch-id syntax is among them; `#id` is reminiscent of the URL form for _goto id_, but `?id`, `@id`, and others are viable.

__Advantages:__ similarity to established try/catch method (but without a try-block); clarity as to which handler is invoked for a given statement; certain statements may be skipped after an error occurs; handlers can return or continue the function.

_Please help clarify (or fix) this proposal sketch, and describe your use cases for its features._

<h3 id="feature-summary">Feature Summary</h3>

__Draft-2, 2018-09-19.__ Discussion following [this comment](https://github.com/golang/go/issues/27519#issuecomment-422892815) below pertains to this draft.

These features meet a large subset of the [_Requirements to Consider for Go 2 Error Handling_](https://gist.github.com/networkimprov/961c9caa2631ad3b95413f7d44a2c98a).

We can select one of several distinct handlers:
```
func f() error {
   v1, #fat := fatalIfError()     // a non-zero value for #id triggers corresponding catch
   v2, #wrt := writeIfError()

                                  // predefined handlers
   v3, #_   := ignoreIfError()    // or log the error in debug mode
   v4, #r   := returnIfError()    // aka #return
   v5, #p   := panicIfError()     // aka #panic
   
   catch fat       { log.Fatal(fat) }           // inferred parameter type
   catch wrt error { conn.Write(wrt.Error()) }
}
```
We can invoke a handler defined at package level (thanks @8lall0):
```
func f() error {
   #pkg = x()
}

catch pkg error {           // package-level handler; explicit type
   log.Println(pkg)
   return pkg               // return signature must match function invoking pkg handler
}
```
We can specify a type for implicit type assertion:
```
   f := func() error { return MyError{} }
   #err = f()
   catch err MyError { ... }
```
We can skip statements on error and continue after the handler:
```
   #err = f()
   x(1)                              // not called on error
   catch err { log.Println(err) }
   x(2)                              // always called
```
We can forward to a different handler (creates an explicit handler chain):
```
   #ret = x()
   if ... {
      #err = f()
      catch err {
         if ... { #ret = err }         // invoke alternate handler
         #ret = fmt.Errorf(..., err)   // invoke handler with alternate input
      }
   }
   catch ret { ... }
```
We can reuse catch-ids:
```
   #err = f(1)
   catch err { ... }
   #err = f(2)
   catch err { ... }
```
We can nest catch blocks:
```
   #era = f(1)
   catch era {
      #erb = f(2)
      catch erb { ... }      // cannot use 'era'; shadowing in catch disallowed
   }
```
We can see everything from the scope where a handler is defined, like closure functions:
```
   v1 := 1
   if t {
      v2 := 2
      #err = f()
      catch err { x(v1, v2) }
   }
```
We can still use Go1 error handling:
```
   v1, err := x()  // OK
   v2, err := y()  // but re-declaration might be abolished!
```

<h3 id="open-questions">Open Questions</h3>

- What catch-id syntax? `#id`, `?id`, `@id`, `id!`, `$id`, ...
  What style for predefined handlers? `#r`, `#p`, `#_`, `#return`, `#panic`, `#nil`, ...

- What handler definition syntax? `catch id [type]`, `catch id(v type)`, `id: catch v [type]`, ...
  Infer parameter from previous stmt? `#err = f(); catch { log.Println(err) }`

- Invoke handler when ok=false for `v, #ok := m[k]`|`x.(T)`|`<-c`, etc?
  Pass a type `error` with context? `v, #err := m[k]; catch { log.Println(err) }`

- Treat parameter as const? `catch err { err = nil } // compiler complains`
  Lets forwarding skip test for nil: `catch err { #ret = err }`

- Require `#id` for return values of type `error`? #20803

- Provide `check` functionality with `f#id()`? e.g. `x(f1#_(), f2#err())`  
  If so, disallow nesting? `x(f1#err(f2#err()))`  
  Allow position selector? `f#id.0()` tests first return value

- Provide more context to package-level handlers, e.g. caller name, arguments?
  `catch (pkg error, caller string) { ... }`

- Allow handlers in defer stack?
  ```
   defer last()              // skip if handler returns
   defer catch errd { ... }
   defer next#errd()         // skip if first() invokes handler
   defer first#errd()
  ```
- Allow multiple handler arguments?
  ```
   #val, #err = f()                 // return values assignable to catch parameter types
   catch (val T, err error) { ... } // either parameter could be non-zero
  ```

### Disallowed Constructs

Declaring or reading a catch-id:
```
   var #err error           // compiler complains
   #err = f()
   if #err != nil { ... }   // compiler complains
   catch err { ... }
```
Multiple catch-ids per statement:
```
   #val, #err = f()   // compiler complains
   catch val { ... }  // if f() returns two non-zero values, which handler is executed?
   catch err { ... }
```
Shadowing of local variables in handlers:
```
func f() {
   if t {
      err := 2
      #err = f()            // OK; #err handler can't see this scope
   }
   pkg := 1                 // OK; #pkg handler (see above) can't see local variables
   err := 1
   #err = f()
   catch err { return err } // compiler complains; err==1 is shadowed
}
```
Self-invocation:
```
   #err = f(1)
   catch err {
      #err = f(2)        // compiler complains
   }
   #err = f(3)
   catch err { ... }
```
Unused handlers:
```
   catch err { ... }         // compiler complains
   #err = f()
   catch err { ... }
   #ret = f()
   catch err { return err }  // compiler complains
   catch ret { ... }
   catch ret { return ret }  // compiler complains
```

<h3 id="discarded-ideas">Discarded Ideas</h3>

Chain handlers with same catch-id in related scopes implicitly, as in the Draft Design:
```
func f() {
   v, #fat := x()
   if v != nice {                      // new scope
      #fat = y(&v)
      catch fat {                      // invoked 1st
         if ... { #fat = nil }         // can skip other handlers in chain
      }                                // no return/exit, continue along chain
   }
   catch fat { log.Fatal(fat) }        // invoked 2nd
}
```

<h3 id="changelog">Changelog</h3>

2018-09-19 draft-2 ([discussion below](https://github.com/golang/go/issues/27519#issuecomment-422892815))
a) Move implicit handler chain to new section "Discarded Ideas".
b) Make continuing after catch the default behavior.
c) Document catch-id reuse and nested catch block.
d) Disallow unused handlers (was "contiguous handlers with same catch-id") and self-invocation.
e) Add `#_` predefined handler to ignore or log input.
f) Add implicit type assertion.

<h3 id="why-not-checkhandle">Why Not <code>check/handle</code>?</h3>

Please read [_Golang, how dare you handle my checks!_](https://medium.com/@mnmnotmail/golang-how-dare-you-handle-my-checks-d5485f991289) for a discussion of each of the following points.

- No support for multiple distinct handlers.
- The last-in-first-out `handle` chain cannot continue a function.
- `check` is specific to type `error` and the last return value.
- The per-call unary `check` operator can foster unreadable constructions.
- The default handler makes it trivial to return errors without context.
- Handlers appear before the calls that trigger them, not in the order of operations.
- The `handle` chain is inapparent; one must parse a function by eye to discover it.

Also, there is relatively little support for the draft design on the feedback wiki.

<h3 id="named-handlers-are-popular">Named Handlers Are Popular!</h3>

At last count, roughly 1/3rd of posts on the [_feedback wiki_](https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback) suggest ways to select one of several handlers:

1. @didenko [github](https://didenko.github.io/grab/grab_worth_it_0.1.1.html)
1. @forstmeier [gist](https://gist.github.com/forstmeier/b6c6a6d2f6f2f72a81a076322959c959)
1. @mcluseau [gist](https://gist.github.com/mcluseau/1c20c3973fa3acb544d0505637be8d67)
1. @the-gigi [gist](https://gist.github.com/the-gigi/3c1acfc521d7991309eec140f40ccc2b)
1. @PeterRK [gist](https://gist.github.com/PeterRK/4f59579c1162cdbc28086f6b5f7b4fa2)
1. @marlonche [gist](https://gist.github.com/marlonche/4e5d4e5aec0555958ec1f181991325f6)
1. @alnkapa [github](https://github.com/alnkapa/Go2ErrorHandlingFeedback/blob/master/README.md)
1. @pdk [medium](https://medium.com/@phlatphrog/handling-more-than-just-errors-in-go-f97c5aa2eac4)
1. @gregwebs [gist](https://gist.github.com/gregwebs/02479eeef8082cd199d9e6461cd1dab3)
1. @gooid [github](https://github.com/gooid/gonotes/blob/master/inline_style_error_handle.md)
1. @spakin [gist](https://gist.github.com/spakin/86ea86ca48aefc78b672636914f4fc23)
1. @morikuni [gist](https://gist.github.com/morikuni/bbe4b2b0384507b42e6a79d4eca5fc61)
1. @AndrewWPhillips [blogspot](http://devmethodologies.blogspot.com/2018/10/go-error-handling-using-closures.html)
1. @bserdar [gist](https://gist.github.com/bserdar/4c728f85ca30de25a433e84ad5a065a1)
1. @martinrode [medium](https://medium.com/@marode/the-return-of-the-return-278b8ae261ab)
1. @dpremus [gist](https://gist.github.com/dpremus/3b141157e7e47418ca6ccb1fc0210fc7)
1. @networkimprov _this page_

And the following posts suggest ways to invoke a handler with assignment syntax:

1. @oktalz [gist](https://gist.github.com/oktalz/f04f36a3c2f61af22c7a6e06095d18eb)
1. @pborman [gist](https://gist.github.com/pborman/c69e79690d86dfc5c371f096be22930c)
1. @kd6ify [blog](http://blog.oldcloudguy.com/2019/04/18/error-handling-in-go-2-draft/)
1. @rockmenjack [github](https://github.com/rockmenjack/go-2-proposals/blob/master/error_handling.md)
1. @the-gigi [gist](https://gist.github.com/the-gigi/3c1acfc521d7991309eec140f40ccc2b)
1. @8lall0 [gist](https://gist.github.com/8lall0/cb43e1fa4aae42bc709b138bda02284e)
1. @dpremus [gist](https://gist.github.com/dpremus/3b141157e7e47418ca6ccb1fc0210fc7)
1. @bserdar [gist](https://gist.github.com/bserdar/4c728f85ca30de25a433e84ad5a065a1)
1. @mcluseau [gist](https://gist.github.com/mcluseau/1c20c3973fa3acb544d0505637be8d67)
1. @didenko [github](https://didenko.github.io/grab/grab_worth_it_0.1.1.html)
1. @gooid [github](https://github.com/gooid/gonotes/blob/master/inline_style_error_handle.md)
1. @Kiura [gist](https://gist.github.com/Kiura/4826db047e22b7720d378ac9ac642027)
1. @networkimprov _this page_

/cc @rsc @mpvl @griesemer @ianlancetaylor @8lall0 @sdwarwick @kalexmills

Thanks for your consideration,  
Liam Breck  
Menlo Park, CA, USA