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: spec: conditional return with return ?err? #70170

Closed
2 of 4 tasks
rad12000 opened this issue Nov 2, 2024 · 5 comments
Closed
2 of 4 tasks

proposal: spec: conditional return with return ?err? #70170

rad12000 opened this issue Nov 2, 2024 · 5 comments
Labels
error-handling Language & library change proposals that are about error handling. LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal Proposal-FinalCommentPeriod
Milestone

Comments

@rad12000
Copy link

rad12000 commented Nov 2, 2024

Go Programming Experience

Intermediate

Other Languages Experience

C#, Java, JS

Related Idea

  • Has this idea, or one like it, been proposed before?
  • Does this affect error handling?
  • Is this about generics?
  • Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit

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

There have been many variations of error handling, proposing an error short circuit syntax such as return?, where the function will return if the last value being returned is an error and that error is not nil. Among the several issues that are had with the proposal is that a return with a question mark is a pretty small thing to notice with the human eye. Additionally, essentially codifying the conventional "error as the last return value" seems to be an issue. This issue hopefully avoids both of the above issues.

Does this affect error handling?

Yes, see above.

Is this about generics?

No

Proposal

Add a special syntax where if a return value is of type error and it is wrapped in question marks (e.g. ?err?), then that return statement only returns if it is the last return in the function body OR the err != nil

Language Spec Changes

See above

Informal Change

Let's take a simple function that contains a couple of error handling checks:

func UserByID(ctx context.Context, requesterID, userID string) (User, error) {
   if err := requesterCanAccessUser(ctx, requesterID, userID); err != nil {
      return User{}, fmt.Errorf("%s cannot access %s: %w", requesterID, userID, err)
   }

   user, err := userByID(ctx, userID)
   if err != nil {
      return User{}, fmt.Errorf("failed to retrieve user %s: %w", userID, err)
   }

   return user, err
}

The above function is very simple, with only a couple of error cases, but it should be sufficient to demonstrate the change. Here is the revised version:

func maybeErrorf(err error, format string, a ...any) error {
   if err == nil {
      return nil
   }

   return fmt.Errorf(format, a...)
}

func UserByID(ctx context.Context, requesterID, userID string) (User, error) {
   err := requesterCanAccessUser(ctx, requesterID, userID)
   return User{}, ?maybeErrorf(err, "%s cannot access %s: %w", requesterID, userID, err)?

   user, err := userByID(ctx, userID)
   return User{}, ?maybeErrorf(err, "failed to retrieve user %s: %w", userID, err)?
   return user, err
}

Of course there is not requirement that you must wrap the result of a function call with the question marks. The following would be allowed

err := foo()
return ?err?

Three notable features/limitations are:

  1. The error does not have the be the last return value return ?err?, user is valid.
  2. Multiple error return values may be wrapped in questions marks, in which case they are OR'd return user, ?err1?, ?err2?
  3. It is not possible to wrap function call with question marks if it returns multiple values. So the following are not allowed
func foo() ([]string, error) {
// some logic
}

func handle() ([]string, error) {
   return ?foo()? // NOT VALID
}
func baz() (error, error) { // Not sure why anyone would do this, but just here to demonstrate this behavior
// some logic
}

func handle() (error, error) {
   return ?baz()? // NOT VALID
}

I feel that if this were used in conjunction with #70169, it would become incredibly valuable. But it is still valuable as a standalone.

Is this change backward compatible?

yes

Orthogonality: How does this change interact or overlap with existing features?

No response

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

It will make it slightly harder since there is a new syntax and an optional return

Cost Description

of course there is a cost related to implementation, and compilation.

Changes to Go ToolChain

No response

Performance Costs

No response

Prototype

No response

@rad12000 rad12000 added LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal labels Nov 2, 2024
@gopherbot gopherbot added this to the Proposal milestone Nov 2, 2024
@seankhliao seankhliao added the error-handling Language & library change proposals that are about error handling. label Nov 2, 2024
@seankhliao
Copy link
Member

This is similar to #21146 #37141 #38349 a gist
I don't think the use of sigils to mark control flow is very Go like.

@seankhliao seankhliao changed the title proposal: spec: Possible return if error is wrapped in ? e.g. ?err? proposal: spec: conditional return with return ?err? Nov 2, 2024
@yesbhautik
Copy link

yesbhautik commented Nov 3, 2024

Instead of using the proposed ?err? syntax, you can achieve streamlined error handling using a helper function:

func checkError(err error, format string, args ...interface{}) error {
    if err == nil {
        return nil
    }
    return fmt.Errorf(format, args...)
}

func UserByID(ctx context.Context, requesterID, userID string) (User, error) {
    if err := checkError(requesterCanAccessUser(ctx, requesterID, userID), "%s cannot access %s: %w", requesterID, userID, err); err != nil {
        return User{}, err
    }

    user, err := userByID(ctx, userID)
    if err := checkError(err, "failed to retrieve user %s: %w", userID, err); err != nil {
        return User{}, err
    }

    return user, nil
}

This approach maintains the explicit error handling pattern while reducing repetition through a reusable function. It keeps the code clear and aligns with Go's existing conventions.

@ianlancetaylor
Copy link
Member

This has some similarities to the recent #70147.

This turns what looks like a return statement into a conditional return statement, based on the use of ? around some expression on the right hand side. This pushes the fact that the statement conditionally changes control flow to the end of a potentially long line. That can be confusing. Even the example above looks confusing with two return statements in a row.

Therefore, this is a likely decline. Leaving open for four weeks for final comments.

@ianlancetaylor
Copy link
Member

No further comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
error-handling Language & library change proposals that are about error handling. LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal Proposal-FinalCommentPeriod
Projects
None yet
Development

No branches or pull requests

6 participants