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 check/with #49091

Closed
steeling opened this issue Oct 20, 2021 · 5 comments
Closed

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

steeling opened this issue Oct 20, 2021 · 5 comments
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal Proposal-FinalCommentPeriod v2 An incompatible library change
Milestone

Comments

@steeling
Copy link

steeling commented Oct 20, 2021

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

@gopherbot gopherbot added this to the Proposal milestone Oct 20, 2021
@seankhliao seankhliao added error-handling Language & library change proposals that are about error handling. v2 An incompatible library change LanguageChange Suggested changes to the Go language labels Oct 21, 2021
@ianlancetaylor
Copy link
Contributor

Some thoughts:

  • for all proposals like this, I think it's slightly unfortunate that check (or try, or whatever) appears before the function being called. It means that reading the code tends to emphasize the error handling. Though no doubt people would get used to overlooking the check keyword.
  • The example handler := formattedError(fmt.Sprintf("copy %s %s: %w", src, dst)) can't be right, as there is no argument corresponding to %w in the call to fmt.Sprintf. I guess it should be %%w, although that is a little subtle.
  • I don't think I understand how this syntax handles the error value returned by a with function. The example shows check io.Copy(w, r) with func(err error) error {return os.Remove(dst)}, but that seems to imply that we will return the error returned by os.Remove. That can't be right. Should this be with func(err error) error { os.Remove(dst); return err }?
  • If I understand the order of execution correctly, on error the function will remove the file and then close it, but that ordering doesn't work on Windows. We need to close first, then remove.

@steeling
Copy link
Author

steeling commented Oct 25, 2021

Thanks for the quick reply! My thoughts inline below

for all proposals like this, I think it's slightly unfortunate that check (or try, or whatever) appears before the function being called. It means that reading the code tends to emphasize the error handling. Though no doubt people would get used to overlooking the check keyword.

I think syntax highlighting helps here as well. One alternative is using single characters, which I know has some pushback, although it could bare similarity to = vs :=. Something like @= has been proposed before. Mixing single symbols with words would be odd, so would likely change with as well. I'm not too picky in this area, as I agree folks would get used to it. Check/with has a nice sound to it though.

The example handler := formattedError(fmt.Sprintf("copy %s %s: %w", src, dst)) can't be right, as there is no argument corresponding to %w in the call to fmt.Sprintf. I guess it should be %%w, although that is a little subtle.

Yup, good catch. Even %%w, remains escaped in future calls so that wouldn't work. that solution would need to become:

func formattedError(fmtStr string, args ...interface{}) func(err error) error {
	return func(err error) error{
		args = append(args, err)
		return fmt.Errorf(fmtStr, args...)
	}
}

and the call to it: handler := formattedError("copy %s %s: %w", src, dst)

I don't think I understand how this syntax handles the error value returned by a with function. The example shows check io.Copy(w, r) with func(err error) error {return os.Remove(dst)}, but that seems to imply that we will return the error returned by os.Remove. That can't be right. Should this be with func(err error) error { os.Remove(dst); return err }?

If I understand the order of execution correctly, on error the function will remove the file and then close it, but that ordering doesn't work on Windows. We need to close first, then remove.

That is correct. The intent was less to show "good code", and more to show how the semantics of this proposal would work

@mrg0lden
Copy link

check on its own with a function that returns no values but an error looks clean to me. However, I don't like the with syntax as it is if err != nil { handler(...) } in disguise; it's just less straightforward. Also, when there are other return values, check is buried in the middle of the line, while most other keywords are usually in the beginning or are in lines that began with keywords, which makes them easier to notice.

@ianlancetaylor
Copy link
Contributor

Thanks for the suggestion, but based on the discussion above, and the emoji voting, this is a likely decline. Leaving open for four weeks for final comments.

@ianlancetaylor
Copy link
Contributor

No further comments.

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 Suggested changes to the Go language Proposal Proposal-FinalCommentPeriod v2 An incompatible library change
Projects
None yet
Development

No branches or pull requests

5 participants