Go: proposal: Go 2: simplify error handling with try err == nil {} except {}

Created on 31 Jul 2019  路  11Comments  路  Source: golang/go

Premised on the expectation that people frequently want to execute a series of statements and check for errors after each statement but frequently want to respond to any error in the same way, we can construct a special statement block to execute these repeated checks for us.

Try statements

"Try" statements specify conditional transfer of control to an "except" clause based on repeated checks of a boolean expression within a block. If the expression evaluates to false after any statement within the block, execution is immediately passed to the "except" branch.

TryStmt = "try" Expression Block "except" Block .

Example:

var err error
var people People
try err == nil {
    jsonFile, err := os.Open("people.json")
    jsonBytes, err := ioutil.ReadAll(jsonFile)
    err = json.Unmarshal(jsonBytes, &people)
} except {
    return nil, errors.Wrap(err, "failed to read people.json")
}

The above translates directly to:

var err error
var people People
{
    {
        jsonFile, err := os.Open("people.json")
        if !(err == nil) {
            goto Except
        }
        jsonBytes, err := ioutil.ReadAll(jsonFile)
        if !(err == nil) {
            goto Except
        }
        err = json.Unmarshal(jsonBytes, &people)
        if !(err == nil) {
            goto Except
        }
        goto NoExcept
    }
    Except:
        {
        return nil, errors.Wrap(err, "failed to read people.json")
        }
    NoExcept:
}

And can be used to replace:

jsonFile, err := os.Open("people.json")
if err != nil {
    return nil, errors.Wrap(err, "failed to read people.json")
}

jsonBytes, err := ioutil.ReadAll(jsonFile)
if err != nil {
    return nil, errors.Wrap(err, "failed to read people.json")
}

var people People
err = json.Unmarshal(jsonBytes, &people)
if err != nil {
    return nil, errors.Wrap(err, "failed to read people.json")
}
FrozenDueToAge Go2 LanguageChange Proposal Proposal-FinalCommentPeriod error-handling

Most helpful comment

I don't think it's a good idea, like try.. catch..., it's bad!

All 11 comments

Has some similarities to #32795, #32804, #33002 and #33266.

Note that this requires a change in the scoping rules. In code like

try err == nil {
    jsonFile, err := os.Open("people.json")
}

the err in the block is being declared with :=. Since it is a different block, that means that it is a newly declared variable, and is not the same as the err variable declared in the outer block.

For example

var err error
if true {
    _, err := os.Open("does not exist")
    fmt.Println(err)
}
fmt.Println(err)

That program will print an error and then print <nil>. The outer err variable is not the same as the inner err variable.

So in order for this to work I think that either try will have not introduce a new block scope, or it will have to somehow inject all variables mentioned in the expression into the inner block. Either seems unusual for Go.

Could the scoping issue be cleaned up by moving the err declaration into the statement? Something akin to:

TryStmt = "try" [ SimpleStmt ";" ] Expression Block "except" Block .

used as:

var people People
try var err error; err == nil {
    jsonFile, err := os.Open("people.json")
    jsonBytes, err := ioutil.ReadAll(jsonFile)
    err = json.Unmarshal(jsonBytes, &people)
} except {
    return nil, errors.Wrap(err, "failed to read people.json")
}

which would then translate to:

var people People
{
    var err error
    jsonFile, err := os.Open("people.json")
    if !(err == nil) {
        goto Except
    }
    jsonBytes, err := ioutil.ReadAll(jsonFile)
    if !(err == nil) {
        goto Except
    }
    err = json.Unmarshal(jsonBytes, &people)
    if !(err == nil) {
        goto Except
    }
    goto NoExcept
    Except:
    return nil, errors.Wrap(err, "failed to read people.json")
    NoExcept:
}

This proposal doesn't allow unique per-statement error handling (annotation) which I and others consider a requirement.

Premised on the expectation

Again and again I see proposals with the same exact premise but don't remember ever needing that kind of error handling in practice. Where does that expectation comes from?

This looks exactly like Swift do/catch and has the same problem for me - it's really rare that I use the same error handling code for multiple call sites. In Swift, lacking any other idiomatic way of handling errors, this forces me to constantly wrap code in do/catch just to add some context. Also the fact that do/try block creates another scope means that any variable declared in that scope is invisible to the catch/except block. That's very annoying and forces you to declare variables outside of do/catch.

@gwax That suggested scoping is still not consistent with the rest of the language. Currently a block scope starts at the {. Writing something like if b := true; b {} effectively creates two blocks.

(Also, note that var err Error is not a SimpleStmt; err := error(nil) would work.)

@ianlancetaylor it sounds like a clean solution, from a scoping standpoint, likely depends on something happening with https://github.com/golang/go/issues/377

I don't think it's a good idea, like try.. catch..., it's bad!

I liked the Handle-Proposal better than this, where you could define an error handler on the same level as the code being executed. What I am seeing here, is people wrapping whole function bodies and having the same handle for different errors, which, in your case, makes sense, but surely not everywhere.

This proposal does not have strong support. The scoping issue remains unresolved. This is a likely decline.

Leaving open for a month for final comments.

There were no further comments.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DemiMarie picture DemiMarie  路  320Comments

derekperkins picture derekperkins  路  180Comments

ghost picture ghost  路  222Comments

tklauser picture tklauser  路  219Comments

ianlancetaylor picture ianlancetaylor  路  519Comments