-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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: define _ on rhs as zero value #19642
Comments
This has been proposed and rejected in the past. I'll try to dig up references. |
Previously: @nigeltao's in Jan 2012: (@robpike had said "not now") @derekchiang in Sep 2013: @kr in Aug 2010: |
As a consideration, a different language proprosal (#12854) for stronger type elliding may solve what this proposal is trying to do. Returning the zero value is only painful for structs. The proposal in #12854 would allow the following: func MyFunc() (otherpkg.ReallyLongStructName, error) {
return {}, errors.New("not implemented")
} |
dsnet, the {} syntax would more clearly differentiate it's use from nil, plus it builds on existing syntax. I love it, a definitely improvement over _. |
|
It's interesting to note that we can get an expression for the zero value for any type by using |
That is interesting, but changing _ to address that disparity feels wrong to me. Not a very scientific comment, I admit. |
I agree, the {} syntax blows overloading _ out of the water in hindsight. |
Personally, I'm an advocate for a new "zero" identifier, similar to nil |
Then propose it, @rogpeppe. |
FWIW, I'm still in favor of the original proposal: a new "zero" identifier, just spelled "_". Just recently, in https://go-review.googlesource.com/c/38280/2/font/sfnt/sfnt.go#867 I changed e.g. Off-topic, but while I think of it, alternative sugar for this particular line could be |
I'm not sure another proposal would add much value. Perhaps the title of this one could be changed to something like "Proposal: define an identifier that means the zero value for all types", because there are a number of possibilities for spelling, but the gist is the same regardless. @nigeltao I agree that can be a pain, particularly when there are many return statements and even more so when returning struct types by value. I've come to like this idiom:
That idiom also makes it easier to add and remove return values. |
Is there any type var x = *new(T) doesn't work? |
@rogpeppe Another option might be to make I don't actually like that option — |
I like the zero predeclared identifier idea. |
I'm not sure if it needs to be a new keyword here. There is With type S struct { }
var v1 S = iota // equivalent v1 := S{}
var v2 *S = iota // var v2 *S = nil
var v3 int32 = iota // 0
func f() (S, int, error) {
return iota, iota, iota // equivalent to return S{}, 0, nil
} With type S struct { }
var v1 S = default // equivalent v1 := S{}
var v2 *S = default // var v2 *S = nil
var v3 int32 = default // 0
func f() (S, int, error) {
return default, default, default // equivalent to return S{}, 0, nil
} Personally I like |
I was going to file an experience report, but found this issue first. I'm definitely in favour of allowing func getFoo() (foo, error) {
if noFoo {
return _, errors.New("no foo")
}
return foo{}, nil
}
f, err := getFoo()
if err != nil {
// oops forgot to handle
}
Save(f) // This could panic |
I agree with @nigeltao, let's allow _ to be a standin for a constant or literal zero value in a return statement. |
I was leery of this proposal because I could only see use for it in return values and for composite literals of non-pointer structs. But I thought of another good use case: generated code. Currently, if you need a zero value in generated code where you are generating code using an arbitrary type, you need to either
The first is complicated for little benefit and the second can make the code feel awkward. With a universal zero value, you can just use that. If Go2 gets generics, the same argument applies except that you would have to use |
Another possibility might be to extend the composite literal syntax to cover non-composite types as well. x := int32{0} // equivalent to x := int32(0)
x := int32{} // omit the initializer for the zero value
x := int32{1, 2} // error
x := &int32{} // equivalent to x := new(int32)
x := &int32{1} // obviates proto.Int32
var x int32 = {} // lightweight zero-values with https://github.com/golang/go/issues/12854
var ch = (chan int){} // equivalent to make(chan int), perhaps?
type T struct {
ch chan struct{
s string
err error
}
}
t := T{
ch: {}, // No need to repeat the channel type (assumes #12854 or #21496).
} I don't think this pulls its weight by itself, but might be of more use with hypothetical Go 2 generics. It could also serve as a replacement for both |
I saw @davecheney was in favour of this in his comment so I'm wondering if this is still being discussed someplace else? If so, a link would be useful. TL;DR: A representation of zero-values would be useful regardless of the syntax used. My take: I have experienced what @dantoye was "complaining" about multiple times, and without even knowing about this thread, I imagined that a good token that could solve this problem would be Also I don't see a problem with it given that it doesn't break compatibility with Go 1 (given that the previous token usage was LHS, although this might cause a little confusion). The only downside is the possible confusion when you have code like: _, err := foo(42)
if err != nil {
return _, err
} But I think go developers are smart enough to understand this, and won't make any pythonish assumptions. In addition, things like: if duration == _ {
doThat()
} could be useful (again, not necessarily suggesting the |
Per the comment above about an expression that produces the zero value of a type (which can be done using
for some generic type
but of course
but that seems absurd. So one advantage of this proposal, if we get generics, is the ability to write
(or any other notation for a general zero value). |
A few different alternatives. // analogous to tagged struct literals.
func f() (X: int, Y: float64, E: error) {
return X: 42
return X: 12, Y: 3.14
return E: errors.New("bar")
}
// _ as zero value of type (i.e. reflect.Zero).
func g() (int, float64, error) {
return 42, _, _
return 12, 3.14, _
return _, _, errors.New("bar")
}
// `zero` predeclared identifier as zero value of type (i.e. reflect.Zero).
func h() (int, float64, error) {
return 42, zero, zero
return 12, 3.14, zero
return zero, zero, errors.New("bar")
}
// i special case error return value to use zero value for excluded return
// arguments.
//
// Note, functions with multiple error values have to be handled specifically in
// the specification; either use only last error, force all error values to be
// returned.
//
// func j() (error, int, error) {
// // only last error
// return errors.New("bar")
// // force all error values
// return errors.New("foo"), errors.New("bar")
// }
//
// Note, this example is not one that we would endorce, however it was included
// as it was part of our discussion and helped us iterate upon different
// alternatives.
func i() (int, float64, error) {
return 42, 0, nil
return 12, 3.14, nil
return errors.New("bar")
} As a follow up to the example analogous to tagged struct literals, it would be interesting to extend this concept to function parameters as well. func j() {
k(A: 13)
k(B: 3.14)
k(B: 3.14, A: 42)
k(42, 3.14)
}
// analogous to tagged struct literals.
func k(A: int B: float64) (X: int, Y: float64, E: error) {
return X: 42
return X: 12, Y: 3.14
return E: errors.New("bar")
} |
Would
If we permit this kind of operation, then Another comment. If we use _ = x
return _ That seems potentially confusing. |
@ianlancetaylor Most simply, _ only has behavior on the LHS right now. This proposal simply defines RHS behavior as an untyped const roughly translating to "zero value". Identical to how an untyped const 123 has different meaning to different numeric Kinds, _ would have different meaning to all Kinds, and always mean the 0 value of that type. There is no ambiguity or conflict. |
I tend to think about Go expressions (including variables) in terms of the sets of values they can take. (In mathematical terms, Go expressions are a join-semilattice.) At the moment, the expression This proposal would add a second meaning for With the meaning of “top of the expression lattice”, then @ianlancetaylor's example of So, under this proposal, in order to determine what If we're going to choose some identifier to mean “any zero-value”, I don't think we should use one that already means “any value at all”. |
@dantoye I did not mean to suggest that there is any ambiguity or conflict. I meant to say that if we permit |
The only place where this will really be used is in a return statement of a function with multiple results. In that particular case, it may be more convenient for the writer of the code to be able to write just '_'`s, but readability is definitively not improved. It gets worse if there are many return values. If there are few return values, it is often better to write out the result values. Finally, there is still the option to use a naked return which is actually better if there are many return values because of the documentation of the return values in the signature. |
If you use a return type from a different package it might not be worth to investigate that certain type For example: func foo() somepackage.Type In this case, the developers of the current package should not be concerned with what that type represents. It might be a type definition like
What happens if you actually assign a value to a return variable? func foo() (i int) {
i, err := bar()
if err != nil {
return
}
return
} Maybe some 3rd party library (opionion) Regarding redability, if this kind of a feature would be accepted and people would know about it, I think it wouldn't be very hard for us to understand when we see Nevertheless, I see no significant difference of readability between say P.S. Why was this issue closed? |
@bogatuadrian The issue was closed because we decided against adopting this proposal. |
Currently, _ only has behaviour on the LHS.
I would like to propose giving it meaning on the RHS as meaning "zero value". This has two main areas of effect.
First, when returning from a function with multiple return values, you can omit the zero-value allocation. For example,
func GetString() (string, error) { return _, errors.New("nostring") }
Understandably you can use named returns as well, but that is less than ideal if you use the named return value as a "scratch space" before deciding to return zero anyway. For example, when building some struct, such as a Request, you might use a named return to build it and, later in the function, come across an error which invalidates your scratch space, in which case you would want to return a zero-value instead of a half-initialized struct with some garbage inside.
Second, when you want to reset a struct, such as when using a pool, you could use _ to restore it to a zero-value.
nil would remain the correct way to create the zero-value for reference types.
The text was updated successfully, but these errors were encountered: