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: remove bare return #21291

Closed
earthboundkid opened this issue Aug 3, 2017 · 56 comments
Closed

proposal: Go 2: remove bare return #21291

earthboundkid opened this issue Aug 3, 2017 · 56 comments
Labels
LanguageChange NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. Proposal v2 A language change or incompatible library change
Milestone

Comments

@earthboundkid
Copy link
Contributor

(I couldn't find an existing issue for this, so please close if this is a duplicate and I missed it.)

I propose getting rid of bare returns. Named return values are great, keep those. Bare returns are more trouble than they are worth, eliminate them.

@gopherbot gopherbot added this to the Proposal milestone Aug 3, 2017
@ALTree ALTree added the v2 A language change or incompatible library change label Aug 3, 2017
@awnumar
Copy link
Contributor

awnumar commented Aug 3, 2017

I would be against this change, for a few reasons.

  1. It's a relatively significant syntactic change. This could cause confusion with developers and contribute to leading Go into the same mess that Python went through.

  2. I do not think that they're useless, why are they more trouble than they're worth? return nils all over the place is a little verbose, don't you think?

@jimmyfrasche
Copy link
Member

@awnumar for 2 this would pair nicely with #21182

@dominikh
Copy link
Member

dominikh commented Aug 3, 2017

return nils all over the place is a little verbose, don't you think?

Simplicity beats verbosity. A bunch of return nil are a lot easier to understand than a bunch of return where one has to chase the latest value of the variable and make darn sure it hasn't been shadowed (intentionally or by mistake.)

@josharian

This comment was marked as resolved.

@tandr
Copy link

tandr commented Aug 3, 2017

I don't think this

func oops() (string, *int, string, error) {
    ...
    return "", nil, "", &SomeError{err}
}

is more readable than

func oops() (rs1 string, ri *int, rs2 string, e error) {
    ...
    e = &SomeError{err}
    return
}

,sorry.

@tandr
Copy link

tandr commented Aug 3, 2017

OTOH - yes, shadowing sucks. It would not make sense to outlaw it outright (generated code comes to mind), but I would gladly vote for at least prohibition of return values names shadowing. Oh, and make compiler generate some diagnostic in all other cases would be nice too :)

Edit: apparently there is #377 that talks about it

@bcmills

This comment was marked as resolved.

@dominikh

This comment was marked as resolved.

@josharian

This comment was marked as resolved.

@leonklingele
Copy link
Contributor

leonklingele commented Aug 16, 2017

A func without return values should still be able to "bare" return:

func noReturn() {
    if !someCondition() {
        return // bail out
    }
    // Happy path
}

@dmitshur
Copy link
Contributor

dmitshur commented Sep 12, 2017

I spent 1.5 minutes looking at the following Go code in Go standard library (from 4 years ago, /cc @bradfitz), in disbelief, thinking there might be a bad bug:

go/src/net/http/server.go

Lines 3158 to 3166 in 2d69e9e

func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}

Specifically, this part:

tc, err := ln.AcceptTCP()
if err != nil {
	return
}

At first glance, it looked to me that on the first line there, new variables tc and err were being declared using short variable declaration syntax, distinct from the named err error return variable. Then, it looked to me that the bare return was effectively returning nil, nil rather than than nil, err as it should've been.

After about 90 seconds of thinking very hard about it, I realized that it's actually correct code. Since the named err error return value is in the same block as the function body, the short variable declaration tc, err := ... only declares tc as a new variable but does not declare a new err variable, so the named err error return variable is being set by the call to ln.AcceptTCP(), so the bare return actually returns a non-nil error as it should.

(Had it been in a new block, it would actually be a compile error "err is shadowed during return", see here.)

I think this would've been much more clear and readable code:

tc, err := ln.AcceptTCP()
if err != nil {
	return nil, err
}

I wanted to share this story because I think it's a good example of bare returns wasting programmer time. In my opinion, bare returns are marginally easier to write (just saving a few keystrokes), but often lead to code that's harder to read compared to equivalent code that uses full returns (non-bare). Go usually does the right thing of optimizing for reading, since that's done much more often (by more people) than writing. It seems to me that the bare returns feature tends to lower readability, so it might be the case that removing it in Go 2 would make the language better.

Disclaimer: In Go code I write and read most often, I tend to avoid having bare returns (because I think they're less readable). But that means I have less experience reading/understanding bare returns, which might negatively influence my ability to read/parse them. It's a bit of catch-22.

@creker
Copy link

creker commented Sep 12, 2017

@shurcooL, interesting example that threw me off as well. For me it was the fact that there're two connection variables tc and c. I thought you made a mistake when copy-pasting the code and instead of c there should be tc. It took me about the same time to realize what that code does.

@awnumar
Copy link
Contributor

awnumar commented Sep 12, 2017

This is probably relevant:

https://blog.minio.io/golang-internals-part-2-nice-benefits-of-named-return-values-1e95305c8687

And the associated discussion:

https://news.ycombinator.com/item?id=14668323

@earthboundkid
Copy link
Contributor Author

From the article:

Note that if you don’t like or prefer the naked return that Golang offers, you can use return oi while still getting the same benefit, like so:

Named return values are great! Bare return of named return values are the problem. :-)

@bradfitz
Copy link
Contributor

@awnumar, that is not relevant. See my comment on Hacker News: https://news.ycombinator.com/item?id=14668595

@pciet
Copy link
Contributor

pciet commented Jan 31, 2018

I think removing these would follow the approach of explicit error handling. I don’t use them.

Code I’ve reviewed on golang-nuts with bare returns isn’t difficult to understand, but parsing variable scope is an unnecessary added effort for readers.

@ianlancetaylor ianlancetaylor changed the title proposal: Go 2: Remove bare return proposal: Go 2: remove bare return Mar 7, 2018
@ianlancetaylor ianlancetaylor added the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Mar 7, 2018
@kelwang
Copy link

kelwang commented Apr 27, 2018

I really love bared return, especially when a function has multiple returns. It’s just making the code much cleaner. The biggest reason, I chose to work with go.

Go tool vet shadow can detect potential shadowed vars. If a func has less cyclomatic complexity, plus some test cases. I couldn’t see it will get any trouble. I could be wrong, but I wish to see some examples to demonstrate how bad bared return could be.

@dmitshur
Copy link
Contributor

I wish to see some examples to demonstrate how bad bared return could be.

@kelwang Did you see my example about ln.AcceptTCP() from 7 comments above?

@kelwang
Copy link

kelwang commented Apr 27, 2018

Hi, @shurcooL, thx

Yeah, I think you made a great point.
But like you said, it's been 4 years. I have a feeling maybe you already get used to it.

I think it's not really an issue for bared return. But a confusion in multiple vars initialization.

For me, the shadow vet tool usually works very well. I never really worry about that.
Maybe we should fill a ticket in go linter to avoid those kind of confusion. whether rename the err, or declare tc on top first. I feel a linter suggestion should be good enough.

@nhooyr
Copy link
Contributor

nhooyr commented May 6, 2018

@kelwang In my opinion, if a function has multiple returns to the point where the return statements are getting ugly, a struct/pointer to a struct should be returned instead over a bare return.

@pam4
Copy link

pam4 commented May 7, 2018

Since #377 has been mentioned, I would argue that the source of confusion in the ln.AcceptTCP example is more about the magic behind := rather than the bare return itself.

I think the ln.AcceptTCP case wouldn't be so bad with a more explicit form of short declaration (proposed in the referenced issue):

func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
    :tc, err = ln.AcceptTCP()
    if err != nil {
        return
    }
    // ...
}

By just looking at a multi-variable := you can't tell which variables are being declared: you need to take into account all the preceding part of the block to know that.
An oversight about where the block boundary is, and you may end up with a hard to find bug.
Also you can't fix a multi-variable := to make it declare exactly what you want; you are forced to give up the short declaration or reorganize the code.

I've seen many proposal trying to address specific consequences of this, but I think the root of the problem is just the lack of explicitness of := (I would also argue that, whenever shadowing is considered a pitfall, it is really just multi-variable :='s fault).

I'm not saying that bare returns are necessarily worth it, I'm just saying that what we are seeing is a compound problem.

@spencerzhang91
Copy link

Regardless of the readability arguments, I think the bare return is less logically consistent and elegant. It's somehow a shaky standpoint. Although, obviously I'm not the only one being subjective here.

@ggicci
Copy link
Contributor

ggicci commented Aug 30, 2018

@tandr I think returning a small struct object is much more clear than having more than three return values.

@bradfitz
Copy link
Contributor

@datatribe, I can't think of any change that would change that. It has ways been an error to miss a return. File a new bug with the details?

@datatribe
Copy link

@datatribe, I can't think of any change that would change that. It has ways been an error to miss a return. File a new bug with the details?

It's entirely possible the example code was no good. Thanks @bradfitz

@jaeyeom
Copy link

jaeyeom commented Jun 25, 2019

I'm not sure if it causes production bugs, but for me, when I see naked return, it usually makes me pause to try to figure out what's happening. I get paranoid about shadowed err working in an unexpected way because I'm not 100% confident in my understanding of how they interact. It makes reading the code much slower.

I agree with you on that. It makes me pause to try to figure out what values are assigned to the named parameter variables. My discomfort reading (especially long) functions with naked returns is that I can't follow easily what are actually returned. Each naked return actually means return p1, p2, ..., pn for named parameters, but sometimes the values of some parameters are obviously literal like return nil, nil, return nil, err, return "", err and return p, nil rather than return p, err. If I write return nil, err, err still might be nil value, but it's clear that the first return value is nil. If naked return is disallowed, the writer would more likely to write literal values in return statement if that's possible. Actually they all did use literal values, when I asked them to avoid naked returns during the code reviews.

When writers write in naked returns, they tend not to think about what are returned. When I asked them to avoid naked returns, now they think about the actual value they want to return and notice that they were returning wrong values like partially constructed values. Isn't being explicit better than implicit?

I know it's recommended to indent error flows but not everyone follows this pattern in reality, especially folks who like to write long functions with lots of naked returns. Sometimes, it's hard to know if it's in an error flow or not by looking many naked returns. I read the code carefully decode it and convert naked returns to non-naked with some literal values, then it's much easier to read. (sometimes it's not possible to figure out the literal values if the code is complex)

I know the documentation comment block mentions what it returns for some meaningful conditions, but not everyone does that. With return statement with some literal values, I can follow that easily by looking at the code.

I know we can use named parameters and non-naked returns together, but not everyone knows that. I often see something like the following, and I think return io.EOF is better.

err = io.EOF
return

That said, I think #28160 is probably a better fix since it's backward compatible and gofmt has already had several minor changes.

I disagree on the other proposal that gofmt should put them back, even if that's gofmt's job. That's because just putting bunch of return p1, ..., pn mechanically won't make the code more understandable.

It's possible that lint tools can check it (nakedret does that), but IMO, naked return has more harm than benefits. I also agree that the benefit of removing naked return may be smaller than the harm breaking the compatibility. But I guess it's easy to fix with go fix even if mechanical go fix won't make the code readable.

Go is very clear and readable language in my opinion. And naked return is a small part of the language that makes me hard to understand the code.

@gopherbot
Copy link

Change https://golang.org/cl/189778 mentions this issue: cmd/go/internal/get: propagate parse errors in parseMetaGoImports

@gopherbot gopherbot removed the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Aug 16, 2019
@gopherbot gopherbot added the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Sep 3, 2019
gopherbot pushed a commit that referenced this issue Sep 11, 2019
The signature of parseMetaGoImports implies that it can return an error,
but it has not done so since CL 119675. Restore the missing error check,
and remove the named return-values to avoid reintroducing this bug in the
future.

Updates #30748
Updates #21291

Change-Id: Iab19ade5b1c23c282f3c385a55ed277465526515
Reviewed-on: https://go-review.googlesource.com/c/go/+/189778
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
tangrufus added a commit to tangrufus/trellis-cli that referenced this issue Nov 4, 2019
@millerlogic
Copy link

I may be in the minority, but I'd actually like to propose the opposite. Functions with named returns should always use bare return. Otherwise, it just looks like the code is lying. You assign values and then potentially silently overwrite them upon return. Here is a bad piece of code to illustrate what I mean,

func hello() (n, m int) {
    n = 2
    m = 3
    return m, n
}

If you'd just use a bare return there, the code makes sense again.

@mvdan
Copy link
Member

mvdan commented Dec 15, 2019

@millerlogic I think that would be far too agressive. For example, there's nothing wrong with code like:

func documentedReturns() (foo, bar string, _ error) {
    [...]
    if err != nil {
        return "", "", fmt.Errorf(...)
    }
}

I also think your point might fit best as a separate counter-proposal, since you're suggesting the opposite of what the current proposal brings forward.

@millerlogic
Copy link

@mvdan Good point, I think there is a middle ground which doesn't involve demonizing them. I think I read an idea similar to: not explicitly returning a value if you previously assigned to the return variable. It might even be implemented somewhere, but I never hit it. So that would mean my hello example would error without a naked return, and your example would succeed because foo or bar weren't assigned to.

@dustinevan
Copy link

dustinevan commented May 23, 2020

I think these should be removed from the tour of go, that way newer go developers aren't encouraged to use these. In practice they're just a cognitive tax. The few I've seen always required my brain to say "wait what?!...oh yeah, naked returns and named return values" I understand maybe leaving them in the tour so everyone knows, but there's an entire page dedicated to them.

@kokizzu
Copy link

kokizzu commented Jul 21, 2022

so.. variable shadowing is the problem, but named return values + naked return got to blame..

@DeedleFake
Copy link

This is actually the one situation where shadowing is rarely a problem. The variables aren't shadowed in the top-level scope of the function, and if they're shadowed and there's a naked return in a sub-scope, it's a compile-time error. The main issue is if the variable is referenced from somewhere else, such as a closure, and then is accidentally shadowed somewhere, but that problem is not at all unique to named returns.

@Diegovsky
Copy link

Diegovsky commented Jul 30, 2022

why not just add a special return syntax for naked returns? That way there is no cognitive overhead of checking named returns since the special return syntax already implies that.

Something like
return !
naked return

Bikeshedding ensues. The goal here is to differentiate naked returns from void returns.

Also this can be opt-in to keep working code working and eventually turned into a lint or error.

@ianlancetaylor
Copy link
Contributor

Removing an existing language feature requires a clear consensus that it is harmful in some way. For example, the feature might be widely misunderstood, and might lead to many bugs in existing code. I don't see that here. I understand that people don't like bare returns--I'm not especially fond of them myself--but I don't see people reporting bugs due to bare returns. So this is not a change that we are going to make, and I'm going to close this issue. Thanks.

@ianlancetaylor ianlancetaylor closed this as not planned Won't fix, can't repro, duplicate, stale Aug 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LanguageChange NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests