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: Single line if variant for returns and other jump statements #62434

Closed
maxnorth opened this issue Sep 3, 2023 · 7 comments
Closed
Labels
error-handling Language & library change proposals that are about error handling. LanguageChange Proposal Proposal-FinalCommentPeriod v2 A language change or incompatible library change
Milestone

Comments

@maxnorth
Copy link

maxnorth commented Sep 3, 2023

Author background

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

What other languages do you have experience with?
C#, JS/TS, Python, Ruby

Related proposals

Has this idea, or one like it, been proposed before?
I've dug around through the past issues quite a bit, haven't seen this specific kind of proposal yet.

The closest proposals have been to preserve existing syntax, but change gofmt to allow single-line if statements.

Does this affect error handling?
It does not meaningfully alter the error handling pattern, though it is largely motivated by an interest in reducing lines of error handling code

How does this differ from previous error handling proposals?
This approach aims to not introduce any new syntax specific to errors. Instead it focuses on a more concise way to conditionally exit a function (and other jump types). I believe there are no fundamental issues in the error handling paradigms in go, there is only noise due to the required 3 additional lines of code in every site where an error needs to be checked and returned.

Is this about generics?
No

Proposal

What is the proposed change?
The proposal is to introduce a new variant of if statement syntax that allows replacing the statement block with one of the control flow keywords that interrupt execution flow, i.e. return, break, continue, or goto. Example:

if err != nil return err

This kind of jump condition would be the only form of if that is allowed to display on a single line.

Who does this proposal help, and why?
This helps reduce noisy boilerplate in code that distracts from the essential behavior of a function. It allows representing common conditional control flow jumps as a single line of code, a considerable improvement over the current requirement of 3 lines for an if statement. It aims to do so with a syntax that is immediately clear to readers, and without introducing magical characters, new keywords, or significant changes to existing control flow and error handling paradigms.

Please describe as precisely as possible the change to the language.

  • This new form of if syntax would allow replacing a block statement with a single jump statement (return, break, continue, or goto) without brackets. This would have to be written on a single line.
  • This form of if statement would not allow a trailing else statement. An else statement in this context serves no purpose. If the condition is true, the flow of execution will be transferred to another location. If not, execution continues.
  • My personal preference would be to not allow an assignment before the condition in this if variant. I am a bit biased against this style in general though, I feel it adds too much horizontal complexity, and would be even more so with a return statement included on the line. I am flexible on this though, could be open to allowing it and leaving its use to developer judgment.

What would change in the language spec?

// original spec
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .

// shorthand for eligible control flow jump statements
JumpStmt = BreakStmt | ContinueStmt | GotoStmt | ReturnStmt

// proposed if statement syntax isolated from existing syntax
IfJumpStmt = "if" Expression JumpStmt .

// proposed syntax combined with existing syntax
IfStmt = "if" ( [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] ) | ( Expression JumpStmt ) .

// alternate option, if we want to allow a SimpleStmt before the Expression in the new syntax
IfStmt = "if" [ SimpleStmt ";" ] Expression ( ( Block [ "else" ( IfStmt | Block ) ] ) | JumpStmt ) .

Please also describe the change informally, as in a class teaching Go.
A common critique of golang is the verbosity of handling errors. Go requires every method that can fail to be handled manually by the caller, and every site of error handling requires an if statement that occupies 3 lines of code, since gofmt does not allow single line if statements. This change provides a new way to write if conditions specifically for return statements and other control flow commands on a single line, which can reduce the lines of code dedicated to this kind of boilerplate by up to 66%.

Is this change backward compatible?
Yes

Code Example - Before

result, err := something()
if err != nil {
    return err
}

result2, err = something2(result)
if err != nil {
    return err
}

for _, item := range result2.Items {
    result3, err := something3(item)
    if err != nil {
        return err
    }

    if result3.Name == "skip-me" {
        continue
    }

    result4, err = something4(result3)
    if err != nil {
        return err
    }

    if result4.Name == "finish-after-me" {
        break
    }

    if result4.Name == "fail-me" {
        return errors.New("example error")
    }
}

Code Example - After

result, err := something()
if err != nil return err

result2, err = something2(result)
if err != nil return err

for _, item := range result2.Items {
    result3, err := something3(item)
    if err != nil return err

    if result3.Name == "skip-me" continue

    result4, err = something4(result3)
    if err != nil return err

    if result4.Name == "finish-after-me" break

    if result4.Name == "fail-me" return errors.New("example error")
}

Orthogonality: how does this change interact or overlap with existing features?
Fairly isolated addition, only adds to the syntax of if conditions. Does change how people will write error handling code, but only insofar as they'll be able to write fewer lines of code using the same patterns.

In terms of readability/scannability, I think this code will still aid quick identification of jump conditions. As the only kind of if condition that displays on a single line, eyes will quickly adjust to recognizing these as conditional control flow points.

Is the goal of this change a performance improvement?
No

Costs

Would this change make Go easier or harder to learn, and why?
Admittedly it would be another variation of valid if syntax to learn, though a modest one and entirely optional. If encountered in code by someone previously unfamiliar, it should be quite clear what it does.

What is the cost of this proposal? (Every language change has a cost).
The compiler and parsers would have to be updated to recognize it as valid syntax, but I would expect this effort to be relatively modest compared other syntax additions (though I'm no expert on this subject).

How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
Most of the above I would imagine. Syntax highlighting still works as expected for me, would imagine goimports would not need modification (speculating here - again, not an expert in these parsing tools).

What is the compile time cost?
I'm speculating again, but I imagine it would be negligible. Maybe even faster if it can skip parsing the block of statements?

What is the run time cost?
Should be none

Can you describe a possible implementation?
This is a bit outside my wheelhouse, but imagine it would entail having parsers supporting seeking an occurrence of one of the supported control flow key words after an if keyword, instead of only seeking an opening { bracket.

Do you have a prototype? (This is not required.)
No

@maxnorth maxnorth added LanguageChange Proposal v2 A language change or incompatible library change labels Sep 3, 2023
@gopherbot gopherbot added this to the Proposal milestone Sep 3, 2023
@seankhliao seankhliao added the error-handling Language & library change proposals that are about error handling. label Sep 3, 2023
@seankhliao
Copy link
Member

Given the similarities to #33113 and #57645, there doesn't seem to be that much different with this besides requiring new syntax?

@maxnorth
Copy link
Author

maxnorth commented Sep 3, 2023

Previous proposals have been around implementing single line returns via gofmt, which I think is non-ideal for a few reasons.

First, how clever should gofmt be about deciding what deserves to be on one line or multiple? Should every return statement be placed on one line, regardless of how complex their return value expression is? Should it only do this for error returns where the other values are nil/empty? Those kinds of decisions feel a bit too nuanced for a formatter. I think giving developers a little leeway to make a judgment call on using a more concise form where warranted would be very beneficial, and this would be a way to do it without fighting against gofmt.

Second, I think having a explicit syntax for this brings some benefits:

  • Easier to enforce rules like not allowing else blocks, which have no relevance in an if statement where the true path is a jump statement.
  • Provides an opportunity to restrict use of assignment before the condition in this variant, to reduce horizontal complexity (my preference, though I did intentionally leave this open for discussion)
  • Makes these kind of jump conditions more visually distinctive (and less cluttered) by removing brackets, plus the fact that the only kinds of if conditions encountered on one line would always imply a control flow decision, which at least in my opinion helps address past critiques around quickly spotting conditional control flow.

@robpike
Copy link
Contributor

robpike commented Sep 4, 2023

The problem with laying out code like this is that the most important part of the line, the action (return, break, etc.) is hidden in the middle of a long line of text rather than being the first thing one sees.

@NullpointerW
Copy link

short line will be nice, i think it can used to only "if err!=nil{return err} " Statement :)

@ianlancetaylor
Copy link
Contributor

The idea of making error checks a single line has been proposed several times, such as in #38151, and always declined. This proposal is slightly different in that it omits the curly braces, but it's not very different. This would also be the only place where a block is permitted but optional. Also the emoji voting is not in favor. Therefore, this is a likely decline. Leaving open for three weeks for final comments.

@lk153
Copy link

lk153 commented Sep 18, 2023

I think following the benefit of S in SOLID principles: just keeping one line of code for handling one thing, in this case just for condition only, makes code clear and readable

@ianlancetaylor
Copy link
Contributor

No change in consensus.

@ianlancetaylor ianlancetaylor closed this as not planned Won't fix, can't repro, duplicate, stale Oct 4, 2023
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 Proposal Proposal-FinalCommentPeriod v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

7 participants