Skip to content

Instantly share code, notes, and snippets.

@byteshiva
Forked from networkimprov/txt.md
Created October 15, 2018 09:19
Show Gist options
  • Save byteshiva/0d0a65771670a7007fde7bcf3f0f8a4d to your computer and use it in GitHub Desktop.
Save byteshiva/0d0a65771670a7007fde7bcf3f0f8a4d to your computer and use it in GitHub Desktop.
Requirements to Consider for Go 2 Error Handling

Requirements to Consider for Go 2 Error Handling

Towards consensus on the requirements for a new errors idiom.

The Go2 Error Handling Overview gives only four rather vague "goals": small-footprint error checks, developer-friendly error handlers, explicit checks & handlers, and compatibility with existing code. And previously @ianlancetaylor made this similar list: golang/go#21161 (comment)

As of this writing, there are almost forty counter-proposals on the feedback wiki for the Go 2 Error Handling Draft Design. Almost none of them discuss the requirements that motivate them, but many imply requirements that the draft design does not fulfill. The Go team might benefit from a consensus on requirements before penning a next draft.

Below is a comprehensive list of possible requirements. Please comment with any others that should be considered.

PS: For those wondering why I've taken the trouble, it's because I had this reaction to the draft design :-)

The List

A. Allow Go1 error idiom to continue working if err != nil { ... }

B. Concise, reusable error "handlers" within a function and/or package.

  1. Uniquely identify handlers, e.g.

    handle h T { return h }     // id is parameter name
    handle h(p T) { return p }  // id is handler name
    h: handle p T { return p }  // id is label
    
  2. Let handler body contain any code that's valid in the enclosing function.

  3. Let handler parameter be of any type, or only type error

    3.1 Perform implicit type assertion if parameter type implements an interface appearing in invocation of handler.

  4. Invoke handler body on any parameter value indicating error.
    Or, for a slice parameter type, on any element value indicating error.

  5. Permit handlers for deferred function calls, e.g.
    defer handle h T { return h }

C. Concise, clear way to select a handler by name. >1/3rd of wiki posts suggest this.

  1. Let assignment invoke a handler.

  2. Let function call invoke a handler.

  3. Define syntax to select a named handler. Options:

    // op: keyword or symbol (e.g. ? @ # and unambiguous pairings)
    //     space between symbol and identifier is not required
    // hname: handler name
    
    v, hname op := f(a)   // assignment
    v, op hname := f(a)
    
    v := op(f(a), hname)  // function call
    v := op(hname, f(a))
    v := f(a) op hname
    v := hname op f(a)
    v := f op hname (a) // n.b.
    

D. Let name of unreachable handler be reused, e.g.

op hname = f(1)
handle hname T { ... }
op hname = f(2)
handle hname T { ... }

E. Let function returning error and a value serve as expression, e.g.

f := func(i int) (int, error) { ... }
x(f op hname (a))
f op hname (a).x()
handle hname error { ... }
  1. Let return value passed to handler default to the last one.

  2. Let index of return value passed to handler be specified, e.g.
    x(f op hname . digit (a))

F. Invoke handler on boolean false parameter for

v, op hname := m[k]
v, op hname := x.(T)
v, op hname := <-c
  1. Generate meaningful parameter value, e.g.
    v, op hname := m[k]; handle hname error { return hname }

G. Let handling of an error skip over any statements between the statement triggering the handler and the handler body. (Entails placement of handler after statements that invoke it.) e.g.

op hname = f()
skipOnError()
handle hname T { ... }
  1. Let handling of an error from a deferred function call skip over any defer statements between the trigger and handler body, e.g.
    defer handle hname T { ... } // triggered by f()
    defer skipOnError()
    defer f op hname ()
    

H. Let handler continue the function. (Entails placement of handler after statements that invoke it.) e.g.

{ if err == io.EOF { break } } // stop enclosing loop, etc
  1. Let a special statement exit the handler, e.g.
    { if ... { break op hname } }

  2. Let a handler invoked by a deferred function call continue up the defer stack, e.g.

    defer runLast()
    defer handle hname T { ... } // triggered by f()
    defer f op hname ()
    

I. Let handler invoke another handler explicitly, e.g.

{ if err != io.EOF { op quit = err } } // invoke "quit" handler
  1. Let handler invoke another handler implicitly, e.g. handlers with same name in related scopes.

J. Let handler perform context accretion, e.g.

{ op quit = fmt.Errorf("blurb: %v", err) }

K. Permit package-level handlers, essentially a custom "predefined handler", e.g.

package main
func f() {
   op hname = x()
}
handle hname T { ... }
  1. Provide caller name to handler, e.g.
    handle (hname T, caller string) { ... }

L. Provide predefined handlers, e.g.

op return = f() // return on error
op panic  = f() // panic on error
op _      = f() // ignore error; for expression f op _ (a)
  1. Disallow the return handler in functions which don't define the requisite type as the last return value.

  2. Goroutine functions and main.main shall not return a value.

  3. Let the ignore handler log errors or all values in a debug mode.

M. Calls to functions returning type error shall invoke a handler for the error value, or assign it to a variable (i.e. not _).

op _ = f() // OK
 err = f() // OK
   _ = f() // compile error
       f() // compile error

N. Define keywords for handle and possibly op above. Consider any, or only C-family, or only Go1 keywords.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment