Go: proposal: Go 2: add "or err: statement" after function calls for error handling

Created on 10 Jul 2019  路  14Comments  路  Source: golang/go

I'd like to draft the following proposal for an expression used for error handling. I believe it achieved the following goals :

  1. It reduces verbosity associated with error handling
  2. It provides separation between business and error logic without hiding any
  3. It is intuitive and should be easy to understand even without prior knowledge.
  4. it clearly shows what is about to happen in any branch.
  5. Useful in functions that do not return errors - such as http handlers.

For disclosure, while I do think the try proposal needs improvement, it is on the right track as it covers the first two of the aforementioned points. I also don't think this will get any traction, though I do hope for some discussion, to at least know it was considered.

Design

The draft introduces a new syntactic form: an optional or <identifier>: <Statement> expression added to the Return, Expression, Assignment and ShortValDecl statements.

Edit: after the first feedback, perhaps the grammar should be changed to or <identifier> <block> to better align with current syntax constructs.

This expression works similarly to try or check. If the preceding expression list produces one or more values, the last value is checked whether it is different than its zero value. If it is not, any previous values except it will be returned or assigned. If the last value is nonzero, it will be assigned to the identifier to be used in the scope of the following statement.

The now well known function can be rewritten as:

func CopyFile(src, dst string) error {
    r := os.Open(src) or err: return err
    defer r.Close()

    w := os.Create(dst) or err: return err
    defer w.Close()

    io.Copy(w, r) or err: return err

    w.Close() or err: return err
}

Edit:

func CopyFile(src, dst string) error {
    r := os.Open(src) or err {
              return err
        } 
    defer r.Close()

    ... 
}

Since the trailing part is a statement in itself, it can be used to decorate errors, handle them (in a code block if needed), breaking and continuing from loops, etc. It also doesn't allow nesting of invocations, which would otherwise reduce readability IMHO.

Drawbacks

One obvious drawback is that it will be more difficult to implement and handle by various tools that a function.
It also may or may not be backwards compatible. Current keywords cannot be used as identifiers,
however this implementation could avoid defining or as another keyword. I do not have enough knowledge to know this if feasible.

Addendum

While everything is up for debate, I haven't so far restricted the last value to only be of the error type. This construct seems handy when obtaining a value from a map as well - either obtaining it or dealing with its absence.

Edit:

Example:

func A(m map[string]interface{}) {
    val := m["key"] or !ok {
        log.Println("Key not found")
        return
    }

   data := val.(MyType) or !ok {
       log.Println("Key value is not of MyType")
       return  
   }

  ...
}
FrozenDueToAge Go2 LanguageChange Proposal Proposal-FinalCommentPeriod error-handling

Most helpful comment

I don't think the syntax fits well with the rest of the language. Currently an identifier followed by a colon is always a label or a case statement; here it is a variable declaration. Currently any conditionally executed statement is always in a block delimited by curly braces; here it is standalone.

All 14 comments

I don't think the syntax fits well with the rest of the language. Currently an identifier followed by a colon is always a label or a case statement; here it is a variable declaration. Currently any conditionally executed statement is always in a block delimited by curly braces; here it is standalone.

I put : since to me, it visually seems to fit well. One solution would be to do what the handle part of the previous draft did:

... or err {
}

I would have preferred if the braces were not mandatory, but I understand the sentiment.

Has some clear similarities to #32848, although it's not identical.

Indeed. While very similar, I'd prefer to keep the try/catch behavior of shifting the last value. And I really do think this construct has potential usability for not just error types.

While this idea is interesting, it doesn't reduce boilerplate that much, compared to:

func CopyFile(src, dst string) error {
    if r, err := os.Open(src); err ! = nil {
              return err
        } 
    defer r.Close()
}

True, but if you look closely, your code will not compile. It is also a bit harder to read, due to the initial if which will always force you to read the whole line to see if there is an initial statement hidden in there. And it's also why I prefer it a block is not mandated on the right side

You're right, I made a mistake, it needs to be like this:

func CopyFile(src, dst string) error {
    if r, err := os.Open(src); err != nil {
              return err
        } else {
        defer r.Close()
    }
    return nil
}

Admitted, an if with an initializer is a bit harder to read, and harder to write, but I don't think this proposal is such a significant improvement to warrant new syntax.

I have a very similar proposal https://github.com/golang/go/issues/33161 so I probably should have just collaborated with you in it.

My main concern with this proposal is that there isn't a visual cue at the beginning of the line that the function usually returns an error as its final parameter.

Questions:
Would you allow this in defer and go functions?

defer w.Close() or err {
    // handle
} 
go  foo() or err {
    // handle
} 



md5-5d0de54a1030633946a78022a31f7d0e



a := foo.(assertionType) or !ok {



md5-a9a3440a3793a2a3d4adbb3066e07d5c



foo{
    Val: (strconv.Atoi(s) or err {
        return err
    }),
}

Altering the defer and go statements could be useful, though it might make the implementation more difficult.

I do however prefer if this suffix works on any type, as stated in the addendum.

On the other hand, it's probably better if inlining isn't allowed. Your examples seems to reduce readability.

The || already means or in convention. It's not appropriate to use or to represent other things. I think I would consider another keyword.

This proposal introduces a new keyword or. I don't think it would be possible to make it not be a keyword.

I think the (modified) syntax is something like

Expression 'or' Identifier '{' Block '}'

The Expression must have at least one value. The Identifier is declared as the type of the last value of Expression. Then the Expression is changed to remove the last value. If the last value is not nil, then it is assigned to the newly declared variable, the Block is executed. The scope of Identifier is only Block.

Can this construct be used outside of an assignment statement? It would be awkward to have a block embedded in a larger expression. But only permitting this in an assignment statement does not seem very orthogonal.

This is a little bit shorter than writing the standard if statement, because you only have to write the identifier once, and you don't have to write != nil. But it's not all that much shorter.

This idea mixes together an assignment statement, an if statement, an implicit conditional test, and a variable declaration. In general, in Go, individual concepts are isolated and can be used independently. This is an odd hybrid of several different ideas.

Can this construct be used outside of an assignment statement? It would be awkward to have a block embedded in a larger expression. But only permitting this in an assignment statement does not seem very orthogonal.

I think that, in order to for this to be useful, it has to cover Return, Expression, Assignment and ShortValDecl statements. Which would, unfortunately, make the spec quite a bit more complex. That might be aleviated if the Expression 'or' Identifier '{' Block '}' was itself an expression, though I don't think that an expression can contain a statement.

In terms of length, I would still prefer that besides a Block, a simple statement should also be allowed for simple handlers.

As noted above, this is an hybrid of different ideas. And it requires a new keyword, which while not impossible, is a higher bar for a language change. It also introduces yet another assignment form. Therefore, this is a likely decline. Leaving open for four weeks for final comments.

-- for @golang/proposal-review

There were no further comments.

Was this page helpful?
0 / 5 - 0 ratings