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: reduce noise in return statements that contain mostly zero values #21182
Comments
Why not named returns? |
I like this idea, but I can't think of any place where I'd use it for anything other than filling in all but the last value. Given that, I think one could reasonably make it a little less general and allow only this form (allowing any expression instead of err, naturally)
Note the lack of comma. I'm not sure whether it's better with a space before "err" or not. |
@OneOfOne Naked returns are fine for very short functions but are harder to scale: it gets too easy to accidentally return partially constructed values or the wrong err because of shadowing. Other than that, or maybe because of that, I like the explicit syntax better. A naked return says "go up to the function signature to see what can be returned, then trace through the code to see what actually gets returned here" whereas @rogpeppe that was the original syntax proposed that I based this proposal off of. I don't like it because it appears to be a spread operator common in dynamic languages so it's a bit confusing. Having the comma makes it superficially more similar to "⋯" in mathematical writing and with like purpose. I agree that this would almost always be used as |
@jimmyfrasche I just don't see that it would ever be used, and given that, the comma seems like unnecessary overhead for what would be a very commonly used idiom. How many places in existing code can you find where eliding all but the first argument (or all but several arguments) would be useful? |
If we permit both |
@rogpeppe @ianlancetaylor that's a fine point. Impossible to implement is justification enough for me. Though it would be possible to implement in some cases, where Another option, that I'm fairly sure is a bad idea, would be to allow "keyed returns" to work in conjunction with named returns, by analogy with keyed struct literals:
though that would interact poorly with the semantics of the naked return. If |
Counter-proposal that has been suggested elsewhere in the past (#19642). Allow E.g. given the function signature
|
When your functions has so many return values that typing them becomes a chore, that's a sign that you need to redesign your function, not the language. |
I think the example was given simply to provide a one of each function signature that is useful as a showcase. While not likely to see so many return values in real world code, returning the zero value of a struct using |
@davecheney indeed. My argument is that the primary benefit of the succinct syntax is that it improves the readability. If it makes it easier to type that's just a bonus. If you see You don't need to double check for things that are suspiciously close to a zero value like It's immediately obvious that the only relevant value is
can be pattern matched by your brain as a unit without having to actually inspect anything. I'm sure we all do that now with similar blocks that contain one or more zero value-like expression. It's bitten me once or twice when I was debugging and my eye glazed over something that looked too close to a zero value making it hard to spot the obvious problem (I of course do not admit publicly to being the person who shadowed nil . . .). I'm fine with how it is, however. This is just a potential way to make it a little bit easier. @mewmew yes this proposal is based on a comment from that proposal (see the History section). I don't particularly see the point of the generic zero value except in the case of returns. It would solve the same problem, of course. (I would like to be able to use |
@davecheney sometimes it's not the number of return values, it's their struct-ness. Typing That said, this particular proposal is not the only way to sooth that chore, as the OP noted. |
Reopening per discussion in #33152. |
I am in favor of allowing only the The main benefit of this for me is that it would let me use a dumb macro to expand ife into if err != nil {
return ..., err
} Yes, a sufficiently smart IDE macro could look at the function return arguments to fill those in for me, but why not just simplify it so only the important information is emphasized? |
What about letting So: func foo() (*T, error) {
if fail {
return errors.New("foo")
}
return new(T)
} |
Does anybody see any problems with this language change? We think we should consider just the simple case: -- for @golang/proposal-review |
@bradfitz that seems like it could cause too much fun when the return signature changes in a long func or one of the return types now satisfies an interface. It would also be unusable with |
That's a feature. |
I would find this unexpected or at least unlearned when s would return the zero string:
It might make more sense to say that named return values maintain their value even with
in that case. |
But in this case, |
@andig would you have this same expectation if naked returns were deprecated? If this proposal were accepted, the use case for naked returns would further diminish. |
There is an interesting hack in the Golang code base to achieve In tls.go func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
fail := func(err error) (Certificate, error) { return Certificate{}, err }
...
return fail(errors.New("tls: failed to find any PEM data in certificate input")) I wouldn't have though this possible until very recently when I learned that Go can accept multiple return values from one function as arguments to another without intermediate destructuring. |
@ianlancetaylor can we get this proposal for |
Moving to main proposal process. |
I favor the direction (mentioned in various ways above; this is not an original idea) that we instead provide a unified way to express the zero of any type. And for my money, the best way to do that is with a predefined identifier called "zero". It would overlap with some uses of "nil", and we'd need to decide what to do about that (I say, nothing), but on the other hand we can open a path to a less confusing model for nil. In other words, rather than say, "return values with zeros are hard so let's fix them, we say "zero values should be easier to use so they work well everywhere, including in return statements". |
The obvious zero values seems to be |
I think that some variant of the original proposal here is better than adding For one, Furthermore, except when writing
Perhaps there are cases I'm not thinking of (maybe it's more common with generics?) but I just can't think of many places I'd use |
I would tend to agree with @robpike The issue imho is overloading a symbol such as Also, I don't find the overall idea that compelling from my own experience writing Go. And that's actually what makes it moot: at best, it just makes things easier to write by handwaving some information. On the topic of the zero identifier, that could help normalize things if writing a zero returning function is not enough (such as: func Zero[T any] (v ...T) T{
var z T
return z
} ) |
Let's take a step back and clarify what problem we are solving. This proposal is trying to solve the issue of letting the programmer communicate that they are just returning an error. It can be used slightly more generally, but that is a less common use case. The main issue is that Go forces us to write down zero values to return an error because we re-use a more general feature to return multiple return values. So the Go language is not optimized for this specific use case, which in some programs accounts for the majority of As @cespare stated, having a generic zero value is not a problem that needs to be more solved more generally. But it also doesn't solve this specific proposal. I don't want an easier way to write down zero- I want to not have to write them at all. However, let's look at a generic zero as a pragmatic solution of using
If there is a choice between adding If we are worried we are solving this issue too specifically, that is certainly a valid concern. If we look around at other languages (for example Rust) we will see that they have solved this problem more generally with tagged unions, a powerful feature to reduce defects by properly modeling state, and the missing feature of Go for anyone that has programmed with them. If this feature were added, it would be easy for end users to define a type Result[T any] enum {
Ok T
Err error
} Writing Will Go be able to add tagged unions or a Result type soon? I hope so. In the next 5 years will Go do this and solve the issue of how to conveniently deal with the result in the calling function? I am doubtful. In the mean time, it would be great if we could add |
If Go programmers move towards using result types instead of multiple returns, then it will be a shame that we added special syntax just for doing a thing that modern Go code won't do anymore. |
I am an optimist, I think Go will figure out how to add tagged unions eventually now that they have Generics. But to be a realist or just a pragmatist, the existing error handling works pretty well other than what is pointed out in this proposal and it is a much bigger change to make it work with a result type- I don't think it is realistic to think that Go will ever make that change. The impact of improving Zero valuesFor the few times that I have needed a generic zero, and several of the uses pointed out in those tickets, defining |
An option would be to add func() (string, int, float64, complex64, error) {
return "", 1..., nil
}() would return |
@zephyrtronium nice suggestion: However, I don't think we want to blur the semantics of It would kind of make sense to use both dot forms at once! Another approach would be to pre-define an array of zeros as |
I opened a proposal to not require |
This proposal has been added to the active column of the proposals project |
If you have a function with things like
then the original proposal would let you write
which is admittedly much nicer, especially when you decide that third result wasn't needed and don't have to update all the return statements to drop the ", nil". The "zero" suggestion doesn't help as much:
still has to get updated. |
I looked through the discussion above and haven't noticed if we attempted to survey the existing corpus of Go code, for if such function is that widely used, so it's worth overloading the My, very naive, guess is that functions like below are what dominate, and cause the main unsatisfaction
With that, IMHO, allowing |
Placing on hold for more thought and later discussion. |
Placed on hold. |
Any updates? I am still hoping for this any of these changes to make the To summarize the whole conversation: The problem is with a return type that has multiple returns like
The original proposal was to allow return ..., err Discussions on this on this included allowing That led to the discussion of allowing zero value in the returns. Either as return _, _, err Or create a new Go builtin return zero, zero, err The complaint with a zero is if your return signature changes all the error lines also need to change. I would be happy with any of them! I personally like the |
I was guilty of that last week as well. |
Update: the current proposal is to permit
return ..., v
to return the zero value for all but the last result, and to returnv
as the last result. The most common use will bereturn ..., err
.A variant under discussion include
return ..., v1, v2
to return zeroes for all but the last N.Another variant is to permit
return ...
to return zeroes for all.In general
...
is only permitted if it omits one or more values--func F() err { return ..., errors.New("") }
is not permitted.Proposal
In return statements, allow
...
to signify, roughly, "and everything else is the zero value". It can replace one or more zero values.This is best described by example:
Given the function signature
func() (int, string, *T, Struct, error)
:return 0, "", nil, Struct{}, nil
may be writtenreturn ...
return 0, "", nil, Struct{}, err
may be writtenreturn ..., err
return 0, "", nil, Struct{X: Y}, nil
may be writtenreturn ..., Struct{X: Y}, nil
return 1, "", nil, Struct{}, nil
may be writtenreturn 1, ...
return 1, "a", nil, Struct{}, nil
may be writtenreturn 1, "a", ...
return 1, "", nil, Struct{}, err
may be writtenreturn 1, ..., err
return 1, "a", nil, Struct{X: Y}, err
may be writtenreturn 1, "a", ..., Struct{X: Y}, err
The following is invalid:
return ..., Struct{X: Y}, ...
— there can be at most one...
in a return statementRationale
It is common for a function with multiple return values to return only one non-zero result when returning early due to errors.
This creates several annoyances of varying degrees.
When writing the code one or more zero values must be manually specified. This is at best a minor annoyance and not worth a language change.
Editing the code after changing the type of, removing one of, or adding another return value is quite annoying but the compiler is fast enough and helpful enough to largely mitigate this.
For both of the above external tooling can help: https://github.com/sqs/goreturns
However, the unsolved problem and motivation for the proposal is that it is quite annoying to read code like this. When reading
return 0, "", nil, Struct{}, err
unnecessary time is spent pattern matching the majority of the return values with the various zero value forms. The only signal,err
, is pushed off to the side. The same intent is coded more explicitly and more directly withreturn ..., err
. Additionally, the previous two minor annoyances go away with this more explicit form.History
This is a generalized version of a suggestion made by @nigeltao in #19642 (comment) where #19642 was a proposal to allow a single token,
_
, to be sugar for the zero value of any type.I revived the notion in #21161 (comment) where #21161 is the currently latest proposal to simplify the
if err != nil { return err }
boilerplate.Discussion
This can be handled entirely with the naked return, but that has greater readability issues, can lead too easily to returning the wrong or partially constructed values, and is generally (and correctly) frowned upon in all but the simplest of cases.
Having a universal zero value, like
_
reduces the need to recognize individual entries as a zero value greatly improving the readability, but is still somewhat noisy as it must encode n zero values in the common case ofreturn _, _, _, _, err
. It is a more general proposal but, outside of returns, the use cases for a universal zero value largely only help with the case of a non-pointer struct literal. I believe the correct way to deal that is to increase the contexts in which the type of a struct literal may be elided as described in #12854In #19642 (comment) @rogpeppe suggested the following workaround:
This has the benefit of introducing nothing new to the language. It reduces the annoyances caused by writing and editing the return values by creating a single place to write/edit the return values. It helps a lot with the reading but still has some boilerplate to read and take in. However, this pattern could be sufficient.
This proposal would complicate the grammar for the return statement and hence the go/ast package so it is not backwards compatible in the strict Go 1 sense, but as the construction is currently illegal and undefined it is compatible in the the Go 2 sense.
Possible restrictions
Only allow a single value other than
...
.Only allow it on the left (
return ..., err
).Do not allow it in the middle (
return 1, ..., err
).While these restrictions are likely how it would be used in practice anyway, I don't see a need for the limitations. If it proves troublesome in practice it could be flagged by a linter.
Possible generalizations
Allow it to replace zero or more values making the below legal:
This would allow easier writing and editing but hurt the readability by implying that there were other possible returns. It would also make this non-sequitur legal:
func noop() { return ... }
. It does not seem worth it.Allow
...
in assignments. This would allow resetting of many variables to zero at once likea, b, c = ...
(but notvar a, b, c = ...
ora, b, c := ...
as their is no type to deduce) . In this case I believe the explicitness of the zero values is more a boon than an impediment. This is also far less common in actual code than a return returning multiple zero values.The text was updated successfully, but these errors were encountered: