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: conditional return and fallthrough cases #27794

Closed
l0k18 opened this issue Sep 21, 2018 · 9 comments
Closed

proposal: Go 2: conditional return and fallthrough cases #27794

l0k18 opened this issue Sep 21, 2018 · 9 comments
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@l0k18
Copy link

l0k18 commented Sep 21, 2018

There is two main threads in the discussions about improvements to Go to put into version 2, generics (cough) and error handling.

This is the latter.

In the most usual, and idiomatic pattern, error handling in Go looks like this:

result, err := DoSomething(With, These, 4, Parameters)
if err != nil {
    return HideUnderTheTable(With, Friends)
}

You can shorten it to this:

if result, err := DoSomething(With, These, 4, Parameters); err != nil {
    return HideUnderTheTable(With, Friends)
}

But what I am suggesting would look like this:

result, err := DoSomething(With, These, 4, Parameters);
return HideUnderTheTable(With, Friends) if err != nil

So it would be, as syntax:

return <statement> [if <condition>]

Optionally the if block could exactly match the header of a standard if block, so this would be the syntax of return afterwards:

return <statement> [if [<statement>;] <condition]

You can also probably imagine these being used similarly to switch/cases like this:

buf := make([]byte, 32)
n, err := rand.Read(buf)
return err if err != nil
return errors.New("did not get 32 bytes") if n != 32

These could be simplified by making the syntax a little different again for lists:

return (
    <condition>: <statement>,
    ....
)

following the convention for import, var, const, and others (and yes, maps). Or maybe parentheses would be more appropriate, but this would replace two or more conditional return statements. These would behave a lot like switch statements but would specifically assist with error handling.

And while I'm on returns, I think that the final return should be implicit with a named return variable.

While I am talking about switch/case blocks, I think also that fallthrough should have a shorter form. Perhaps by adding 'if' as a possible label header, which implies fallthrouogh.

I find myself using switches a lot and I think that if certain cases, such as the return, and the fallthrough, had more concise syntax, that people would use them and all the error handling gotchas could be eliminated.

@l0k18
Copy link
Author

l0k18 commented Sep 21, 2018

It occurred to me that the same thing as with the optional statement in if blocks could be used as syntax, except the first clause is a conditional that determines if the return is done:

return <condition>; <statement>

though that isn't consistent with using the colon for the list syntax:

return (
    <condition>:
        <statement>
)

The simple conditional return I can see getting a lot more use, I'm not sure a compound format would be a useful thing (since switch/case/returns can do the same), but being able to abort a return makes error handling shorter.

It's basically a switch case return with implicit fallthough. This is basically what most error handling blocks are. That is, it returns from the function if a condition is true, if not, execution continues.

In current Go this has to be done this way:

if <condition> {
    return <statement>
}

This construction is possibly one of the most common patterns you will see in Go source code, and it is only because you can't return on condition but on condition return - this is a major obstacle against consistent error handling.

@bcmills bcmills changed the title Go 2 proposal: Conditional return and fallthrough cases proposal: Go 2: Conditional return and fallthrough cases Sep 22, 2018
@gopherbot gopherbot added this to the Proposal milestone Sep 22, 2018
@bcmills bcmills added the v2 A language change or incompatible library change label Sep 22, 2018
@bcmills
Copy link
Contributor

bcmills commented Sep 22, 2018

CC: @mpvl

@perholmes
Copy link

perholmes commented Oct 13, 2018

I mega-support this. This is easily the most obnoxious part of Go because especially while running MySQL queries, every single statement turns into 5 lines of code, with obligatory if err != nil { return err } spread over 4 lines.

This reduces code readability by a lot, because you can only have 6 or 7 steps of actual code logic on a screen, the rest peppered with boilerplate if err != nul { return err }

I appreciate the Go designers' desire for a clean language, but this puts a 400% tax on almost every line of code that can produce an error.

It's not necessary with exception handling, I agree that it's a dirty design pattern. But we're left with nothing, except this constant drone ya-da-ya-da-ya-da-ya-da of if err != nil {return err}

Keep in mind also that gofmt is hard biased against multiple statements on one line, and gofmt is also a near-mandatory tool to use. So it's a perfect storm that results in some pointlessly verbose code that's significantly less readable.

I could live with either solution:

1.) Conditional returns.

2.) A change to gofmt that allows multiple statements per line.

@perholmes
Copy link

perholmes commented Oct 13, 2018

Yet another bad thing with Go insistence about 4-line error handling is that the only real way to reduce the amount of boilerplate code is to flip the logic so you don't return, but do nesting hell instead, which is another anti-pattern:

if doSomething() {
    if doSomething() {
        if doSomething() {
            if doSomething() {
               if doSomething() {
              }
            }
        }
    }
}

Go really is conspiring to force you into some very poor error handling patterns unless you want every single statement to be appended with 4 lines of boilerplate if (err != nil) {return err}

  • Gofmt doesn't allow multiple statements on one line
  • No exception handling (that's OK)
  • No conditional returns
  • Forced into either disabling gofmt, nesting hell, abusing the panic/recovery system, or terrible readability where most of the code on your screen is not actually code.

80% of the code on my screen right now is boilerplate that you have to think around when you're trying to understand the code flow. I feel like Go is winning the battle and losing the war in this regard.

I think the Go designers' idealism is getting just a little bit out of hand with error handling, because there's no combination of tools that allows you to make readable code when there's a lot of error handling (MySQL, I/O etc).

@l0k18
Copy link
Author

l0k18 commented Oct 13, 2018

I think that bearing in mind that some of the neat multi-clause things like the map key check and the pre-test if statement clause hint towards an optional test clause for returns, it's such a small change and enables elimination of several kinds of vertical bloat of the code (both error handling and fallthrough statement all by itself pointlessly in a switch case block).

It amounts to something like a macro to implement it, as it is simple to parse it back to if condition { statement }. It's a breaking change but it would not be difficult even to write a preprocessor that rewrites it back if for whatever reason you must use an older version of the compiler.

@Azareal
Copy link

Azareal commented Oct 14, 2018

What about if err != nil { return err } ?

@l0k18
Copy link
Author

l0k18 commented Oct 15, 2018

gofmt always adds newlines to the return call in an if block.

@l0k18 l0k18 closed this as completed Oct 15, 2018
@l0k18 l0k18 reopened this Oct 15, 2018
@ianlancetaylor ianlancetaylor changed the title proposal: Go 2: Conditional return and fallthrough cases proposal: Go 2: conditional return and fallthrough cases Oct 16, 2018
@ianlancetaylor
Copy link
Contributor

We already have a way to write a conditional return:

if err != nil {
    return err
}

This proposal suggests a second way to write a conditional return:

return err if err != nil

There's no obvious reason to only permit the trailing if on return statements; it is generally useful. But then, there is no obvious reason to permit the trailing if at all, since we already support the preceding if. In general we prefer to have fewer ways to express a certain kind of code. This proposal adds another way, and the only argument in favor is to remove a few lines. We need a better reason to add this kind of redundancy to the language. We aren't going to adopt this.

Is there an existing issue for formatting if err != nil { return ... } on a single line? I know I've seen that suggested a few times.

@l0k18
Copy link
Author

l0k18 commented Oct 17, 2018

I agree that the 'statement after the return' is ambiguous. And

return condition; statement

isn't terribly clear either. But what about a new keyword to indicate this - I can't think of anything short and snappy. And to go back to what has already been done what about this then - try to look at it like you never saw it before:

if statement; condition {}

or the other notable one:

x, ok := MapVariable[key]

or for that matter

a, b = 1, 2

I considered and rejected the if after return because of how it muddles up the syntax of return and if. You could change it to 'when' maybe?

return expression when condition

When is a word that I can't see as any kind of competition for memorable variable names. I thought about ? but I don't like that one and it sorta collides with c++'s ? if true statement : if false statement. The other thing about when is it implies concurrency.

But it feels non-idiomatic to me. But I'm aiming at several constructs with this, both fallthrough cases and if {return}. I personally would like the fallthrough removed, as it is completely commutable with a regular if statement in almost every way. So instead of fallthtrough you have return conditional; expression.

This clarifies the use cases for switch/case blocks. Plus it removes a more wordy alternative to an if block that if anyone's honest, will tell you they don't use though it seemed like a nice idea at first. I certainly thought that. I use cases a lot, for anything more than one condition. I tried fallthrough a few times and eventually decided it was clearer to use an if before the switch anyway.

The other thing I just thought of was using a comma to separate the condition, but then I remembered tuple returns. Colon came up as well, but it isn't logical as the symbol is generally used as part of declarations or section headings, mainly definitions. Actually on that note I know it's C idiom but since a case condition has to be on its own line in go anyway, actually it could also be removed. It has one weird and cool feature in vscode - when you type a case it unindents it immediately you put the character at the end. But it's entirely unnecessary. It implicitly is followed already by a semicolon.

It's my view that valid and coherent changes to the language that break code really need to pop, because of how well designed it already is. And I would submit to you that you may even want to remove some things. The colon on cases and the optional inline if statement are good examples. The latter particularly becomes redundant with a conditional return for a large amount of use cases, and the remaining cases I can suggest some patterns with methods and pass-throughs that can make this happen anyway.

and goto and labels! Damn, who actually uses those? If they were kept I would want to change the label so it's on the same line, we don't need labels on separate lines because most of the time you'll probably decide to write a function instead.

@golang golang locked and limited conversation to collaborators Oct 17, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

7 participants