Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Go 2: error handling by assignment, with named handlers #32500

Closed
networkimprov opened this issue Jun 8, 2019 · 11 comments
Closed
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@networkimprov
Copy link

networkimprov commented Jun 8, 2019

Background

The feedback wiki for the Go 2 Draft Design proposing check/handle saw the emergence of two recurring themes, documented here:
https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback#recurring-themes

13 responses suggested a nil test triggered by assignment, to reduce if err != nil boilerplate:

f, ? := os.Open(...) // one of many syntaxes suggested

17 responses suggested named error handlers, to enable multiple error paths within a function:

func example(c net.Conn) error {
   a := check os.Open(...) ? openErr // one of many syntaxes suggested
   ...
   b := check os.Open(...) ? openErr
   ...
   check io.Copy(c, a) ? copyErr
   check io.Copy(c, b) ? copyErr
   ...
   handle openErr { ... } // log error
   handle copyErr { ... } // log error, write error msg to c
   return nil
}

Others have newly reiterated both themes in comments on the try() proposal #32437.

Herein is a composite of these recurring themes. I believe it bears consideration in light of the apparently deep resistance to try().

Summary

v, flag [op] := f()

try v, flag [op] := f() // alternative, to indicate control-flow within statement

  • flag indicates that a zero-value test shall be performed on that return value,
  • op gives an optional procedure to invoke on a non-zero value,
    e.g. return, panic, or a named handler (defined at package-level or in current function)
  • try is a new keyword
  • v is an ordinary variable
  • := is any declaration or assignment operator
  • func f() (int, error)

Possible syntax (choose one)

   f, #return  := os.Open(...)
   f, ?return  := os.Open(...)
   f, @return  := os.Open(...)
   f, {return} := os.Open(...)

   try f, #  := os.Open(...)
   try f, ?  := os.Open(...)
   try f, @  := os.Open(...)
   try f, {} := os.Open(...)

Possible ops

   f, #return := os.Open(...)
   f, #panic  := os.Open(...)
   f, #hname  := os.Open(...) // invoke user-defined handler
   f, #       := os.Open(...) // ignore, or invoke "ignored" handler if defined

   try f, #      := os.Open(...) // return
   try f, #panic := os.Open(...)
   try f, #hname := os.Open(...)
   try f, #_     := os.Open(...) // ignore

Example handlers

   handle fatal (err error) {
      debug.PrintStack()
      log.Fatal(err)
   }

   handle log (err error, clr caller) { 
      fmt.Fprintf(os.Stderr, "%s got %s, continuing\n", clr.name, err)
      // may return the parent function or not, to support recoverable errors
   }
   // caller is a handle-specific type with results of runtime.Caller()

A sub-expression syntax (which has caused complaint in the try() comments) is possible, but only recommended for ignore or panic ops:

f(os.Open#(...))       // ignore
f(os.Open#panic(...))
f(os.Open#p(...))      // any leading substring of panic?

f(#      os.Open(...)) // prefix alternative
f(#panic os.Open(...))

f(os.Open(...) #)      // postfix alternative
f(os.Open(...) #panic)

About that Pesky New Symbol

People seem to dislike #op or @op or ?op, so let's ponder an alternative.

We could define three new keywords (for ops return, panic, invoke). They constrain the return value tested to the last one. To date, try & must have been suggested for return & panic, and we could employ check for invoke. One can already use _ for op ignore, although it looks like a bug, and blank-assigned values can't be logged for debugging.

try   f := os.Open(...)            // return

must  f := os.Open(...)            // panic

check f := os.Open(...) else hname // invoke hname

   f, _ := os.Open(...)            // wait, is that a bug?

I'd rather define one new keyword (or none) and see a symbol with an op that names its action.

Work in Progress

As the try() proposal may be adopted, I leave this proposal in an incomplete state. I may develop it further if the Go team expresses interest.

More possibilities are described in Requirements to Consider for Go 2 Error Handling, but most of them are not "recurring themes".

Who's @networkimprov? Liam is the author of

@gopherbot gopherbot added this to the Proposal milestone Jun 8, 2019
@gopherbot gopherbot added Proposal v2 A language change or incompatible library change LanguageChange labels Jun 8, 2019
@deanveloper
Copy link

deanveloper commented Jun 9, 2019

My main issue with this (and the reason I think that this has so many 👎 s) is because it relies heavily on symbols. Very few things in Go rely on unconventional symbols. The only one that I think Go actually uses (I may be wrong here) is the <- operator for channels - IIRC all other symbols in Go are well-known features in other languages.

@networkimprov
Copy link
Author

networkimprov commented Jun 10, 2019

These four were new to me <- := ... _.

The cost of introducing a new symbol must be weighed against the benefit of cleaner error handling. (Note that a lot of gophers don't believe that eliminating if err != nil is a benefit.)

If the designers proposed this, many would see it as a great advance; succinct yet noticeable :-)

@deanveloper
Copy link

If the designers proposed this, many would see it as a great advance; succinct yet noticeable :-)

I'm not sure that authorship is the reason that people aren't liking it, don't blame it on that when there haven't been any responses. Whether or not you mean it this way, it comes off as incredibly selfish to blame others for not liking something that you made. (Selfish may not be the right word but I couldn't figure out a better one).

Also, have you seen the +1 to -1 ratio, and the comments, on #32437? Just because designers propose something doesn't mean that people will like it.

These four [operators] were new to me

In terms of the operators you mentioned - the ... is pretty common (it's called a spread operator and many languages, including old ones, have it). I've seen _ have special meaning in languages as well, typically denoting "something people shouldn't care about". In Python and Javascript they are used to hide private variables (since they don't have concepts of private variables in objects (except new JS, although it's a relatively unknown feature)). In Python however it's used the exact same way as it's used in Go. IIRC C#, Rust, and other languages have a similar feature that uses _ the exact same way that Go does. However if someone isn't well versed in those languages they can definitely be confusing.

I'll admit that := is a bit weird, however. In some languages, := is used for assignment while = would be used for comparison (aka Go's ==). It's also used in mathematics sometimes. I'll admit it's pretty obscure though.

The cost of introducing a new symbol must be weighed against the benefit of cleaner error handling. (Note that a lot of gophers don't want cleaner error handling of any kind.)

But what I'm getting at is that Go code can typically be explained with relative ease because it uses very few obscure symbols, nearly everything is typed out. I don't need to explain to someone what something like f := os.Open#panic(...) means because everything in Go is written out. Explaining that statement would sound something like "well we call os.Open, and the # means that if the error, provided by os.Open, is not nil, then we do something. In this case, we panic". And that'd only get worse as we add complexity.

However, when we write out the full statement, it becomes quite obvious:

f, err := os.Open(...)
if err != nil {
    panic(err)
}

Now it reads as "we call os.Open, and panic if the error is not nil". No need to explain what weird symbols mean.

I am someone who thinks that error handling is fine as it is, so take my words with a grain of salt.

@networkimprov
Copy link
Author

networkimprov commented Jun 10, 2019

Gosh, I wasn't blaming anything on anyone. I was alluding to the high esteem the community has for the designers!

IIRC, the early thumb count on try() was >50% up.

@deanveloper
Copy link

deanveloper commented Jun 10, 2019

Good to hear haha. Comments over text always come off differently as how they are meant.

@networkimprov
Copy link
Author

Added above: About that Pesky New Symbol

People seem to dislike #op or @op or ?op, so let's ponder an alternative.

We could define three new keywords (for ops return, panic, invoke). They constrain the return value tested to the last one. To date, try & must have been suggested for return & panic, and we could employ check for invoke. One can already use _ for op ignore, although it looks like a bug, and blank-assigned values can't be logged for debugging.

try   f := os.Open(...)            // return
must  f := os.Open(...)            // panic
check f := os.Open(...) else hname // invoke hname
   f, _ := os.Open(...)            // wait, is that a bug?

I'd rather define one new keyword (or none) and see a symbol with an op that names its action.

Dean, this is not addressed to you; please ignore it :-)

@deanveloper
Copy link

Hey hey hey, no need to make it personal haha. I actually quite like it. I know there are a lot of compatibility arguments against something like that, but it really does look nice IMO. I sorta liked solutions like that in the other error handling proposal too.

@fulldump
Copy link

Having a special syntax to avoid explicit and in place error handling is IMHO a more way to postpone the problem, a more way to increase tech debt.

@yaa110
Copy link

yaa110 commented Jun 26, 2019

this proposal suggests a Perlish style for Go and makes it tricky to work!

@ianlancetaylor
Copy link
Contributor

I don't think I like this proposal, but I don't see why it needs a special character. You could just rely on the fact that return and goto are already keywords, and on the fact that you can't put a function on the left hand side of an assignment.

    x, return := F()
    x, goto handler := F()
    x, panic() := F()

Of course it would be necessary to define what values are returned for the return case, and we would have to provide some way to access the error returned by F in the handler. And it would be nice to use that in the panic call. And then we could even write

    x, return fmt.Errorf("my error: %v", err) := F()

One approach would be to use named result parameters, although that confuses the error result of F with the error result of the caller.

All that said I think it's unlikely that we would adopt this. It seems too ad hoc for the problem that it solves.

Speaking only for myself.

@ianlancetaylor
Copy link
Contributor

This proposal creates a sort of special assignment destination: if you assign to one of these special destinations, then something different happens. We would have to understand how this works in package scope. We would have to understand the order of operations. Presumably the special destination can be used anywhere on the left hand side of an assignment, so what happens with

    #return, #return := F()

or even

    #return, #return := F1(), F2()
    #H1(), #H2() = F3()

In short this seems to require quite a few more rules regarding evaluation order.

We only have a few special characters left unused. Is this special purpose operation the right way to use one of them?

This could probably be made to work, but it comes at a significant cost in complexity of assignment statements. Also the test against a zero value seems somewhat ad hoc, and seems to be the opposite of what one might want for non-interface values such as booleans.

There doesn't seem to be much enthusiasm for this proposal. It's not clear that people want this kind of shortcut in the first place, as we've learned from other proposals in this space.

-- writing for @golang/proposal-review

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

7 participants