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 if" #70147

Closed
buffos opened this issue Oct 31, 2024 · 8 comments
Closed

proposal: spec: conditional return with "return if" #70147

buffos opened this issue Oct 31, 2024 · 8 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

@buffos
Copy link

buffos commented Oct 31, 2024

Introduction

  • Would you consider yourself a novice, intermediate, or experienced Go programmer? intermediate
  • What other languages do you have experience with? C, C++, Python, Javascript, Haskel, SML, Ruby, the list is big
  • Would this change make Go easier or harder to learn, and why? It does not affect it
  • Has this idea, or one like it, been proposed before? Yes in somewhat different wording. The idea is just to forward a return statement.
  • Is this change backward compatible? yes
  • Is the goal of this change a performance improvement? No
  • Does this affect error handling? Yes and No. It's not only about error handling but it will drastically simplify and improve error handling. Instead of 20000 places where error handling is done, it can be delegated to a couple of functions. When you want to refactor, you will not have to change all the places where error handling is done, but a centralized place.
  • Is this about generics? No

Proposal Details

First of all, I would like to note that I was very reluctant to make this step, but I really think it is simple and will simplify code a lot.

Goal: Simplify repetitive if blocks that return.

Example: Error handling.

if err!=nil {
 // do stuff
return value1, value2, err
}

We can generalize the inner block of the if function, but we still have to process the result with an if statement to exist the outer function. To many lines of code for something that could be a one-liner.

How it will work

func SomeFuncName(x,y,z any) (int,interface{},err){
 a,err:= someCall(x,y,z)
 return if errorHandler(err)
}
// the function can have any number of variables of any type 
// as long as the first value it returns is bool and the rest of the parameters are exactly the ones of the caller
func errorHandler(err) (bool, int, interface{}, err) {
 // do stuff
 if err!=nil {
   return true,0,something,err // the outer function will return using 0,something,err as the return values
 }
  return false,0,interface{},nil // we will not return so the other values are not relevant
 }

The requirements would be

  • The function after the return if call should return N+1 arguments (N is the number of arguments the outer function returns)
  • The first return value should be a boolean
  • The other return values should have the same type as the corresponding outer function.

This will greatly reduce the number of lines we write in an application. Especially in error handling.
We will write a few general Errorhandlers and then simply use return if ErrorHandler(err)
If we want to change our handling, we would have to change just a few functions.

Of course, this is not only helpful in error handling. It will also help reduce the cognitive workload of functions with multiple if/else or switch statements.

Keyword to use

If there is no language ambiguity in using return if, then it's the most natural choice.
We can also use return on or concatenate and use a single word.

@gopherbot gopherbot added this to the Proposal milestone Oct 31, 2024
@seankhliao seankhliao changed the title proposal: core/lang. return if keyword proposal: spec: conditional return with "return if" Oct 31, 2024
@seankhliao seankhliao added LanguageChange Suggested changes to the Go language error-handling Language & library change proposals that are about error handling. labels Oct 31, 2024
@seankhliao
Copy link
Member

Please fill out https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing language changes

@seankhliao
Copy link
Member

I'm not sure how you'd have only "a few general Errorhandlers" when the return types need to match. Generics aren't that useful since we don't infer based on return types.

@ianlancetaylor ianlancetaylor added the LanguageChangeReview Discussed by language change review committee label Oct 31, 2024
@ianlancetaylor
Copy link
Member

There is a lot of overlap with #53017 and #68745.

@buffos
Copy link
Author

buffos commented Oct 31, 2024

I'm not sure how you'd have only "a few general Errorhandlers" when the return types need to match. Generics aren't that useful since we don't infer based on return types.

Let's take for example a handler. I would be using gin to make things simpler for the example.

func (h *H) someHandler(c *gin.Context) {
 var req someType
 err := c.ShouldBindJSON(&req)
 return if HandleErrorBadJSON(err) // the function will handle the error and return the request
// we can go to one line also, but this is not the goal
 
 err:= doValidations(req)
 return if HandleBadRequest(err) // we can have more function params if we like
 
 resp,err;=h.CallControllerFunction(req)
 HTTPResponse(resp,err,c) 
 // this last one does not even need a return statement
 }

PS. I understand that Go is not very keen in hiding details or making something one line for the shake of being shorter.
Its not only about errors.

Its like a nested for loop. Sometimes you want to break from inside the loop.
There is no such mechanism for nested calls.

@apparentlymart
Copy link

I agree with @seankhliao that it seems like this would essentially require each function to have at least one error handling function of its own, since the error handling function is overloaded with two responsibilities: it must both decide whether to return and provide the values to return.

I expect that in practice most authors would use this feature with inline closures instead, if I'm correct in assuming that the error handlers will typically be tightly coupled to a single function that calls them. For example:

func SomeFuncName(x, y, x any) (int, any, error) {
    errorHandler := func (err error) (bool, int, any, error) {
        if err != nil {
            return true, 0, "something", err
        }
        return false, 0, nil, nil
    }

    a, err := someCall(x, y, z)
    return if errorHandler(err)
}

Some additional examples that are more like real code that you might write using this pattern could help counter my assumption that error handlers would be tightly-coupled to their callers. It's possible that the contrived example you chose made this seem less flexible just because of the rather unusual combination of return types you chose. 🤔


Subjectively, I find it awkward that the error handler function is required to return values for all of the caller's return values even when the first return value is false.

I understand that this is somewhat consistent with how it's typical (though not universal) for a function to return zero-values for everything except its error return value when the error is non-nil, but in this particular situation those values are always immediately discarded with no opportunity to make use of them, which seems confusing and redundant.


Comparing this to the (already-declined) #53017, I find the older proposal's "Iteration 2" very similar but with a more concise syntax that I generally prefer.

I believe the above example would've been written as follows under that proposal:

func SomeFuncName(x, y, x any) (int, any, error) {
    a, err := someCall(x, y, z)
    return 0, "something", err if err != nil
}

The author of that proposal rejected that form in favor of the final "Iteration 3", which I think would appear like this:

func SomeFuncName(x, y, x any) (int, any, error) {
    a, err := someCall(x, y, z)
    return if err != nil { 0, "something", err }
}

This variation was declined on the grounds that it's only marginally different than the current idiom:

func SomeFuncName(x, y, x any) (int, any, error) {
    a, err := someCall(x, y, z)
    if err != nil {
        return 0, "something", err
    }
}

All of these have the distinct advantage of keeping the expressions defining what to return on error close to the return statement. The two examples from the earlier proposal were also considerably more concise than this new proposal.

I agree with the reasons given for declining the other proposal, but I'm afraid I find this new proposal less readable than either of the previous proposal's ideas due to moving the expressions that decide the return value of one function into another function potentially quite far away from the one I'm currently trying to read.

Therefore I'm afraid I'm voting 👎 on this one. 😖

@ianlancetaylor
Copy link
Member

As noted above, there is some overlap with previous issues.

The phrase return if F makes it sounds as though if F returns true we should return. While that is somewhat true, we are also using the results of F. It is also rather confusing to have the boolean result change the control flow.

The emoji voting is not in favor.

For these reasons, 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