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: introduce try/guard keyword for error handling #39890

Closed
andig opened this issue Jun 27, 2020 · 9 comments
Closed

proposal: Go 2: introduce try/guard keyword for error handling #39890

andig opened this issue Jun 27, 2020 · 9 comments

Comments

@andig
Copy link
Contributor

@andig andig commented Jun 27, 2020

Go has suffered from seen a number of Go2 error handling proposals. I'm joining the chorus as I had the feeling that a comparatively less disruptive permutation of the discussion has not come up before. I'd be totally happy if this got closed right away, in that case sorry for wasting everybody's time.

Goals of this proposal:

  1. decrease verbosity, i.e. less typing and clearer structure of the non-error case
  2. full control over error handling, including annotating the error
  3. avoid hidden or non-local control flow magic

Before going to the details, I have considered:

  • #33161 (block-scoped error handling) which attracted a comment for verbostity in #33161 (comment) but also was compared with Swift's guard in #33161 (comment).
  • #32437 (A built-in Go error check function, "try") which I personally feel introduces too much non-local error handling (using defer) and allows for hard to read nested calls of try()
  • #33387 (simplify error handling with try err == nil) which attracted this notable comment #33387 (comment) (unique per-statement error handling)
  • #36284 which partially failed due to #36284 (comment) (fails to address the most common case of uniquely annotating errors in-situ.)

Proposal

This is an adapted version of #33161 (comment), I couldn't find an original proposal to this case (/cc @carlmjohnson).

In short, I'm proposing to introduce a try keyword similar to Swift's guard that will be implemented as an error-checking and error-handling specific alternative to the existing if statement:

  • try must me followed by an assignment expression where the last assignment parameter must be an error and can be omitted
  • if the error parameter is omitted, a non-nil error (and other zero value or initialised variables) will be returned alongside with it. This does not require the error to be named in the API
  • if the try statement has a handler block, the block will be executed/ the error can be handled similar to if err != nil and returned or not. This typically requires the err parameter to be named.
  • unlike the if statement, a special scoping rule allows the assigned target variables (right term?) to escape the block (I understand this is similar to swift). This includes the error variable if it is named.

As such, this proposal is similar to #33161 (comment), taking the comments regarding control flow from #33161 (comment) into account.

Syntactically, the two following statements are equivalent:

try foo := bar()
try foo := bar() {
    return
}

It would look like

TryStmt = "try" AssignmentExpression [ Block ]

Examples

CopyFile

func CopyFile(src, dst string) error {
	try r := os.Open(src) // returns the error
	defer r.Close() // returns the error

	try w := os.Create(dst) // returns the error
	defer w.Close() // returns the error

	try io.Copy(w, r) // returns the error
	try w.Close() // returns the error
}

Update: Obviously this example needs an additional rule for error precedence during defer (tbd). Updated the defer Syntax. Using try inside deferred functions follows the same rules as everywhere else.

Hex

func main() {
	try hex, err := ioutil.ReadAll(os.Stdin) {
		log.Fatal(err)
	}

	try data := parseHexdump(string(hex)) {
		log.Fatal(err)
	}

	os.Stdout.Write(data)
}

Some thoughts

As @carlmjohnson said:

  • I think if something like this is done, it should be called guard because that's a name used by another language.
  • Probably it's not enough better than `if to be adopted.
@gopherbot gopherbot added this to the Proposal milestone Jun 27, 2020
@gopherbot gopherbot added the Proposal label Jun 27, 2020
@ianlancetaylor ianlancetaylor changed the title proposal: Go2: introduce try/guard keyword for error handling proposal: Go 2: introduce try/guard keyword for error handling Jun 27, 2020
@carlmjohnson
Copy link
Contributor

@carlmjohnson carlmjohnson commented Jun 28, 2020

What are the semantics of defer try w.Close()? Does it only assign an error if the existing error is non-nil? That would solve a real problem IMO.

I think the thing that really killed the last try proposal was that it made code coverage wrongly consider a try line “covered” even if it didn’t take the error path. Is there a way to handle that for try without a block in this proposal? Maybe auto-rewrite as a return block in coverage?

@andig
Copy link
Contributor Author

@andig andig commented Jun 28, 2020

I think the thing that really killed the last try proposal was that it made code coverage wrongly consider a try line “covered” even if it didn’t take the error path.

@carlmjohnson could you link that proposal? Not sure which one you're referring to- I feel it's the one I couldn't find in my research?

@carlmjohnson
Copy link
Contributor

@carlmjohnson carlmjohnson commented Jun 28, 2020

#32437

@andig
Copy link
Contributor Author

@andig andig commented Jul 7, 2020

What are the semantics of defer try w.Close()? Does it only assign an error if the existing error is non-nil? That would solve a real problem IMO.

@carlmjohnson removed that syntax as it obscured the flow of the error variable (and if ... defer is not legal either).

@simskij
Copy link

@simskij simskij commented Jul 7, 2020

I'm still not sure I get what adding some kind of try semantic to the language would solve that is not already addressed by if err != nil? One of the key strengths of go, in my opinion, is that it's strict and simple, without the unnecessary cognitive overhead introduced by offering multiple ways to solve the same problem.

@bitfield
Copy link

@bitfield bitfield commented Jul 8, 2020

it's not enough better than if to be adopted

I think this is exactly right. It doesn't go far enough to satisfy the people who find writing if err != nil irritating, and it's entirely unnecessary for everybody else.

@andig
Copy link
Contributor Author

@andig andig commented Jul 9, 2020

It doesn't go far enough to satisfy the people who find writing if err != nil irritating

I beg do disagree. I don't think you could reduce this much more unless sacrificing other aspects:

  • 1 line instead of three for the simple case
  • no need for err!=nil in the complex case
  • clear flow from code outline

... and it's entirely unnecessary for everybody else.

The number of error handling proposals, including those of go team members, shows that there is a wider need.

@simskij
Copy link

@simskij simskij commented Jul 9, 2020

The number of error handling proposals, including those of go team members, shows that there is a wider need.

At the same time, the fact that the proposals keep getting shot down indicates that the need so far has not outweighed the added complexity.

@andig
Copy link
Contributor Author

@andig andig commented Jul 9, 2020

...absolutely. And since this one doesn‘t even develop traction in terms of comments or votes I‘m simply closing it to save everybody‘s time. Thanks for sharing your thoughts!

@andig andig closed this Jul 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
6 participants
You can’t perform that action at this time.