Skip to content

proposal: Go 2: Error handling check/with #49091

Closed
@steeling

Description

@steeling

The proposal:

The overview of the error handling problem described here is spot on. The verbosity of error handling can make it tough to spot bugs. However, the design didn't allow for much flexibility in that the check used the most recent, in-scope handle. That doesn't provide a lot of flexibility to users. We can do better, by treating handle as a function (one that either takes a single error argument, or variadic args with the requirement the last arg is the error to check).

I propose we introduce 2 new keywords, check and with:

When we check a return value, we can specify how we want to handle it, like so:

func myFunc() error {
   check doSomething() // return if error

   check doSomething with func(err error) {return fmt.Errorf("error doing something %w", err)}
}

More information and examples in the template below

Would you consider yourself a novice, intermediate, or experienced Go programmer?

5+ YOE with Go in production/enterprise

What other languages do you have experience with?

Java, C, python, dart/typescript/javascript,

Would this change make Go easier or harder to learn, and why?

Easier, minimal new complexity, while readability is greatly increased

Has this idea, or one like it, been proposed before?

Yes, many error proposals exist

If so, how does this proposal differ?

At this point, all of the various error proposals are slightly nuanced. I believe this proposal offers better flexibility in specifying the error handler, while maintaining clear readability.

Is this change backward compatible?

Adds new keywords

Show example code before and after the change.

See the example from the this previous draft's overview for the before.

After:

func formattedError(fmtStr string) func(err error) error {
	return func(err error) error{
		return fmt.Errorf(fmtStr, err)
	}
}

func CopyFile(src, dst string) (err error) {
        handler := formattedError(fmt.Sprintf("copy %s %s: %w", src, dst))
        r := check os.Open(src) with handler
        defer r.Close() with handler // could alternatively call formattedError directly.

        w := check os.Create(dst) 
        defer w.Close()

                        
        check io.Copy(w, r) with func(err error) error {return os.Remove(dst)}
        check w.Close() with func(err error) error { return os.Remove(dst)}
        return nil
}

How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?

anything that parses go code.

What is the compile time cost?

Likely minimal. Not sure if this would cause issues with unbounded lookahead though

What is the run time cost?

Depends on implementation/negligible

How would the language spec change?

As described

Orthogonality:

how does this change interact or overlap with existing features?

check would look similar to a return. Take some time to wrap our heads around that, but a welcome improvement to the current 3-liner.

Is the goal of this change a performance improvement?

No

Does this affect error handling?

Yup!

If so, how does this differ from previous error handling proposals?

Again differences are nuanced but a cleaner syntax (IMO), and an expressive with check that allows flexibility in determining the handle function.

Is this about

generics?

No

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions