golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.22k stars 17.47k forks source link

spec: add untyped builtin zero #61372

Closed rsc closed 11 months ago

rsc commented 1 year ago

I propose to add a new predeclared identifier zero that is an untyped zero value. While nil is an untyped zero value restricted to chan/func/interface/map/slice/pointer types, zero would be an untyped zero value with no such restrictions.

The specific rules for zero mimic nil with fewer restrictions:

That's it. That's all the rules.

Note that assignability includes function arguments and return values: f(zero) and return zero, err are valid.

See CL 509995 for exact spec changes.


This proposal addresses at least three important needs:

  1. Referring to a zero value in generic code. Today people suggest *new(T), which I find embarrasingly clunky to explain to new users. This comes up fairly often, and we need something cleaner.

  2. Comparing to a zero value in generic code, even for non-comparable type parameters. This comes up less often, but it did just come up in cmp.Or (#60204).

  3. Shortening error returns: return zero, err is nicer than return time.Time{}, err.

More generally, the zero value is an important concept in Go that some types currently have no name for. Now they would: zero.

Because zero is not valid anywhere 0, "", or nil are valid, there will be no confusion about which to use.


I'm not claiming any originality in this proposal. Others have certainly suggested variants in the past, in quite long discussions. I'm not aware of any precise statement of the exact rules above, but I won't be surprised if one exists.

A brief comparison with earlier proposals:

fzipp commented 1 year ago

as soon as you have two different generic types to make zeros for now you have to write things like

  var zeroK K
  var zeroV V
  return zeroK, zeroV, false

And to me at least that makes clear we have a problem. We can disagree of course, but most people I've spoken to about this believe there's a problem worth solving here: users shouldn't have to puzzle through one or more of these workarounds.

It's a bit cumbersome, but I'd just write it and then forget about it and get on with my life (but that's just my personal tolerance). Generic functions are only a fraction of all Go functions being written, and generic functions that need to create generic zero values are a fraction of generic functions, and those with more than one type parameter even more so. Also, the zero value creation part can already be implemented as a library function today.

So the only part of the proposal that brings something interesting to the table is the comparison aspect.

Merovius commented 1 year ago

@rsc

A few people, including @AyushG3112 and @DeedleFake, https://github.com/golang/go/issues/61372#issuecomment-1636993251 https://github.com/golang/go/issues/61372#issuecomment-1636994974 again at iszero, but to me that's a non-starter. We already have a way to test for equality to a zero value for many types, and that's == 0 or == nil. To fit into the existing Go language, any solution here should look like those.

Just to add another option to the mix: It would be possible to expand nil but only to comparisons. That is, we could say "x == nil is always allowed and is true, if x is a zero value". That would address the same problem as iszero and would look like the existing ways. It also would not introduce another predeclared identifier and it would avoid the type-checking pitfalls of making nil assignable to anything.

The downside is that you could then write x == nil even if x = nil would be invalid. It also does not address construction of zero values.

(Yet Another Compromise would be to expand comparison to nil, but only to type parameter typed variables, which is the only place where it is not yet possible to write anything)

earthboundkid commented 1 year ago

Instead of focusing on the upsides/downsides of expanding nil, what are the downsides of adding zero? It’s a new predeclared identifier (con). It can be used in confusing ways where nil is more appropriate (con). Are there any other cons?

AyushG3112 commented 1 year ago

Rather than adding an identifier for zero value, does it make sense to add a "comparable to nil" constraint for slices, channels, maps etc?

That'll allow something like this to work, and act as a counterpart to comparable.

func isZero[T comparable | nilcomparable](val T) bool {
    nc, ok := any(val).(nilcomparable)

    if ok {
        return nc == nil
    }

    var zero T
    return val == zero
}
mateusz834 commented 1 year ago

@AyushG3112 This is not going to work with custom struct types (with slices, maps etc. fields)

Merovius commented 1 year ago

@AyushG3112 interface{ comparable | nilcomparable } is not legal, as you can not have comparable in a union (and nilcomparable would likely have the same problems comparable has for that). The reasons are complicated, but it's not something we're going to fix in this issue. Also, the .(nilcomparable) wouldn't be possible just like .(comparable) is not possible, as neither of those are types… well, really, there are many reasons that wouldn't work.

AyushG3112 commented 1 year ago

@Merovius @mateusz834 yes I know, what I'm saying is whether we can add support for this in the language over the zero identifier? Was just a suggestion.

Merovius commented 1 year ago

@AyushG3112 And what I responded is "no, we can't for a bunch of reasons, among them the fact that comparable can not happen in a union". Those reasons are complex. We won't be able to litigate them out of existence as a side-note in this issue.

AyushG3112 commented 1 year ago

I don't mind the idea of a "zero" identifier as long as it's constrained to comparison. I just don't like the idea of being able to return it or use it as an actual value because

  1. It introduces 2 different ways of doing the same thing - for example return time.Time{} and return zero will now be equivalent with no proper language guidelines on what is idiomatic and why.
  2. It makes it easy to gloss over what the value being returned or passed actually implied or means to the consumer over when you explicitly instantiated it.
fzipp commented 1 year ago

I don't mind the idea of a "zero" identifier as long as it's constrained to comparison. I just don't like the idea of being able to return it or use it as an actual value because [...]

@AyushG3112 In principle I agree with that, but unfortunately it would probably mean that every month someone not knowing the context of this decision would create an issue asking to make zero also usable as an actual value, because of the dangling carrot. It would require at least a FAQ entry with an explanation. The problem probably wouldn't exist with iszero(), but I agree with Russ that if v == zero reads better than if iszero(v).

jimmyfrasche commented 1 year ago

@Nasfame consider var x struct { f, g func() } No generics involved but there is currently no way to check if x is its zero value currently aside from x.f == nil && x.g == nil. Under the proposal you could simplify that to x == zero.

Also every case where this is useful in generic code holds for generated code where the code generated doesn't involve generics. Being able to always write return zero, zero lets a code generator not have to worry about a lot of special cases.

fzipp commented 1 year ago

I disagree. I use zero checks and non-zero checks very often that I occasionally messes up if err==zero and if err!=zero.

Doesn't that mean that you have a general difficulty with equality / inequality in C-style programming languages?

fzipp commented 1 year ago

My point is that equality operators are susceptible to typos

Many things in programming are susceptible to typos. If this was a huge problem we should have different operators or no operators at all. Anyways, this is getting off-topic.

I personally would not want to see people writing if !iszero(err) instead of if err != nil in Go. This possibility is a reason against iszero() in my book. I hadn't thought of that before.

jimmyfrasche commented 1 year ago

Unnecessary uses of iszero(x) or x == zero/return zero/etc. are all completely trivial things to lint. It would be very easy to mechanically enforce only using these where they are the only option.

AndrewHarrisSPU commented 1 year ago

consider var x struct { f, g func() } No generics involved but there is currently no way to check if x is its zero value currently aside from x.f == nil && x.g == nil. Under the proposal you could simplify that to x == zero.

Memoization or some similar things jumped to mind, it's narrow but in the right spots a huge win - I think x == zero would be really helpful for things like this.

fzipp commented 1 year ago

what's wrong with that? Is it the flexibility ?

The addition of another way to do the same thing and, if someone actually does this, the break with an existing established pattern. I'm also convinced that someone who mixes up != and == would also mix up !iszero() and iszero(), especially if applied to error checks, because the convention of the unhappy path in the if branch would require !iszero(err), not iszero(err).

soypat commented 1 year ago

what's wrong with that? Is it the flexibility ?

All codebases use err != nil as an idiom for checking errors. Bringing in a different way is dissonant to long time gophers and at best confusing for new gophers learning the language as there would be two different ways of doing the same thing.

That said, I think it's very unlikely that people would start using !iszero(err) since most IDEs have static analyzers that could warn against this usage favoring the all too established err != nil.

jimmyfrasche commented 1 year ago

I could write func an(err error) bool { return err != nil } and then use if an(err) { all over my code.

fzipp commented 1 year ago

I could write func no(err error) bool { return err != nil } and then use if no(err) { all over my code.

Actually your implementation should be named thereIsAn.

jimmyfrasche commented 1 year ago

lol, oops. The dangers of Sunday-posting!

Merovius commented 1 year ago

Can I kindly suggest to let this issue cool down for a couple of hours? I don't believe this back-and-forth is going to change many minds and it is prone to sniping, repetitiveness and moving off-topic.

merykitty commented 1 year ago

I don't understand the paranoid regarding the possibilities to do the same thing in slightly different manners. While it is slightly inconvenient in the presence of inconsistencies, the differences among x == 0, x == zero or x == nil is the least thing to be worried about. They are just different names to the exact same thing, they are exactly the same, just like I can declare a variable with any name I like and it would not in any way raise the question of why this variable is named indices and not indexSlice. Languages are powerful, there are always different ways to do the same thing in any programming language, or else it will be useless. In Go we have math.Min and built-in min to get the minimum of 2 floating point numbers with mostly-the-same-but-slightly-different semantics, var zero T, *new(T) or even a custom-made zero[T]() to get the zero value in a generics-compatible way, etc.

Regarding this specific proposal, the most important point being addressed is the ability to compare any variable against its type's default value in a generics-compatible manner. If the construct is not allowed to appear in non-generics code, then the restriction itself would be an inconsistency, and the simple mental model of generics being a copy-and-paste solution for parametric polymorphism is violated. On the other hand, if the construct is allowed in non-generics code, then it would inevitably lead to different ways to compare a variable against zero, regardless of the construct being a == nil, a == 0 or isZero(a).

In conclusion, my point is that there would inevitably be inconsistencies if we want the most important issue to be addressed, but different spelling of the default variable is the most minor thing that needs to be concerned with and it should not interfere with the decision of including this feature in the language.

nathan-cormier commented 1 year ago

I'm wholly favor of this proposal, @rsc is spot on when he says:

the zero value is an important concept in Go that programs currently have no name for. Now they would: zero.

Full disclosure, I'm the author of one of his linked related proposals (https://github.com/golang/go/issues/60695).

ydnar commented 1 year ago

Given the choice between:

  1. A new predeclared identifier zero that is a superset of nil
  2. Relaxing the constraints for nil to be defined as the untyped zero value for any type

The second option makes more sense because it simplifies the language, which I’d argue is in the spirit of Go. Given that nil already means the zero value for non-pointer types, the author or reader would not have to consider whether the type is pointer-ish when assigning or comparing to the zero value.

Sure, var s string = nil feels weird today, but does it feel more weird than uppercase exported symbols or gofmt? Would it feel weird in 6 months or a year?

davidmdm commented 1 year ago

Would it make sense to try and distinguish zero from nil instead of making it a superset of nil? That zero is for zero-values of non-pointer types: structs, ints, floats, complex, strings, bools, and so on. Therefore if assigning it to a pointer type, it would create a new zero non-nil pointer?

Ie:

var x int = zero // x == 0
var x *int = zero // x == new(int)

It's not so much that I am convinced that this is a great idea, but that the only issue I see with this proposal is the overlap between zero and nil. This is just an attempt at preserving as much orthogonality in the language design as possible.

This would ensure that zero values are non-nil.

The big downside would be that the following would be confusing:

var x, y *int = zero, zero
x == y // false 

Just food for thought.

gophun commented 1 year ago

"the zero value is an important concept in Go that programs currently have no name for", at least in the general case. @gophun seems to be arguing that maybe it shouldn't be such an important concept.

I didn't mean to dispute that the zero value is an important concept in Go. The zero value as a default for variables is great to avoid questions about uninitialized memory, and the rule to make the zero value useful is great, too. Using it as a throwaway value in return SomeStruct{}, err is fine, too.

But what bothers me is giving special meaning to the zero value in the form of conditions based on it. The signifier of meaning should be something else, such as the ok in the "comma ok" idiom, or the err in return SomeStruct{}, err, not the zero value.

justinfx commented 1 year ago

I keep seeing statements that the primary goal is comparisons, and the suggestion to just add an iszero builtin. But can I ask, why then can't we consider allowing "truthy" tests to be a shorter acceptable equivalent? Instead of x == zero or iszero(x), why can't we just allow all types to do if x (not zero value) and if !x (zero value).

earthboundkid commented 1 year ago

I keep seeing statements that the primary goal is comparisons, and the suggestion to just add an iszero builtin. But can I ask, why then can't we consider allowing "truthy" tests to be a shorter acceptable equivalent? Instead of x == zero or iszero(x), why can't we just allow all types to do if x (not zero value) and if !x (zero value).

See https://pkg.go.dev/github.com/carlmjohnson/truthy#example-Value 😃

justinfx commented 1 year ago

See https://pkg.go.dev/github.com/carlmjohnson/truthy#example-Value 😃

Right, so that exists via library. And if we are talking about the option of adding zero or iszero() to achieve the same result of testing for the zero value, why can't we consider making the feature of your library a builtin language feature so we can directly test values without the library?

aohorodnyk commented 1 year ago

I might miss it in the thread or from the description.

But since zero value will be a universal value for any type, what will we see in this use case?

package main

import "fmt"

func main() {
    val, err := returnZero()
    fmt.Println(val == zero, val == nil, err == zero, err == nil)
}

func returnZero() (any, error) {
    return zero, nil
}

I would expect to see true for all of these conditions.

Could someone clarify please, what do we expect to see there?

DmitriyMV commented 1 year ago

Would it make sense to try and distinguish zero from nil instead of making it a superset of nil? That zero is for zero-values of non-pointer types: structs, ints, floats, complex, strings, bools, and so on. Therefore if assigning it to a pointer type, it would create a new zero non-nil pointer?

Ie:

var x int = zero // x == 0
var x *int = zero // x == new(int)

It's not so much that I am convinced that this is a great idea, but that the only issue I see with this proposal is the overlap between zero and nil. This is just an attempt at preserving as much orthogonality in the language design as possible.

This would ensure that zero values are non-nil.

The big downside would be that the following would be confusing:

var x, y *int = zero, zero
x == y // false 

Just food for thought.

That will kill the ability to use this in generic code.

AlexanderYastrebov commented 1 year ago

BTW I think adding zero identifier would break code like this:

    var zero int64 = 0
    objectMeta.SetDeletionGracePeriodSeconds(&zero)

k8s API objects have a lot of pointer fields and &zero is used for initialization

apparentlymart commented 1 year ago

@AlexanderYastrebov thankfully a "predeclared identifier" is different in Go than a keyword, in that it is valid to shadow a predefined identifier.

Your example would therefore still compile and have the same meaning as before, but it would mean that for the remainder of this lexical scope zero has a different meaning than what's defined in the specification (it's now literally int64(0)), which could be confusing for maintainers of that particular function.

I wonder if the compiler could have a special diagnosis for a situation where assignment type checking fails for an identifier named zero that isn't the predeclared identifier, explicitly noting that zero was redefined with a definition location included in the error message.

DmitriyMV commented 1 year ago

@AlexanderYastrebov thankfully a "predeclared identifier" is different in Go than a keyword, in that it is valid to shadow a predefined identifier.

Your example would therefore still compile and have the same meaning as before, but it would mean that for the remainder of this lexical scope zero has a different meaning than what's defined in the specification (it's now literally int64(0)), which could be confusing for maintainers of that particular function.

I wonder if the compiler could have a special diagnosis for a situation where assignment type checking fails for an identifier named zero that isn't the predeclared identifier, explicitly noting that zero was redefined with a definition location included in the error message.

go vet could probably mark this code, but the compiler doesn't do anything about redefining len or any, so I don't see any reason for doing something else here.

ianlancetaylor commented 1 year ago

@aohorodnyk I agree that all of those conditions should be true.

Merovius commented 1 year ago

@justinfx

I keep seeing statements that the primary goal is comparisons, and the suggestion to just add an iszero builtin. But can I ask, why then can't we consider allowing "truthy" tests to be a shorter acceptable equivalent? Instead of x == zero or iszero(x), why can't we just allow all types to do if x (not zero value) and if !x (zero value).

I'd say the same arguments as against expanding nil to be assignable/comparable to any type apply, just more.

Also, FWIW: I wouldn't say that allowing comparisons is the primary purpose of this proposal (though I agree, obviously, that many people say that). It's just the only thing that has an objective value, in that it allows to write code that is not currently possible to write. The other purposes - decrease in verbosity, having a clear and universal name to refer to an important concept - are subjective and harder to quantify, but that doesn't make them less important. I'd say.

DmitriyMV commented 1 year ago

will this work for zero as currently comparison operators are only supported for comparable types

func SomeGenericFunction[T any](P T) bool {
  var z1 T = zero
  return P == z1
}

No - while P is a variable that comparable with untyped zero, z1 is an instantiated variable of type T and those are not comparable unless specifically marked as comparable in generic code.

@ianlancetaylor I think the behaviour of zero can be replicated with this

func zero[T any]() (zero T) {
  return
}

No - since this code will not work:

func compareToZero[T any](val T) bool {
    return val == zero[T]() // this will not compile
}
aohorodnyk commented 1 year ago

will this work for zero as currently == is supported only for comparable types

func SomeGenericFunction[T any](P T) bool {
  var z1 T = zero
  return P == z1
}

It related to my question.

I might miss it in the thread or from the description.

But since zero value will be a universal value for any type, what will we see in this use case?

package main

import "fmt"

func main() {
  val, err := returnZero()
  fmt.Println(val == zero, val == nil, err == zero, err == nil)
}

func returnZero() (any, error) {
  return zero, nil
}

I would expect to see true for all of these conditions.

Could someone clarify please, what do we expect to see there?

In the proposal we see:

Referring to a zero value in generic code. Today people suggest *new(T), which I find embarrasingly clunky to explain to new users. This comes up fairly often, and we need something cleaner.

Looks like it's valid and open question about the behavioral.

DmitriyMV commented 1 year ago
  • If zero will return new(T) then in case of reference it won't match.
  • If zero will return nil it will match (see my question).

zero will return *new(T) and it will match, just like it does today https://go.dev/play/p/v93_g19Aj9p

aohorodnyk commented 1 year ago

will this work for zero as currently comparison operators are only supported for comparable types

func SomeGenericFunction[T any](P T) bool {
    var z1 T = zero
    return P == z1
}

No - while P is variable that comparable with untyped zero, z1 is an instantiated variable of type T and those are not comparable unless specifically marked as comparable.

@ianlancetaylor I think the behaviour of zero can be replicated with this

func zero[T any]() (zero T) {
    return
}

No - since this code will not work:

func compareToZero[T any](val T) bool {
    return val == zero[T]() // this will not compile
}

You stick with T any part where you are right. I thing the question should be focused on top of this part of the proposal:

Referring to a zero value in generic code. Today people suggest *new(T), which I find embarrasingly clunky to explain to new users. This comes up fairly often, and we need something cleaner.

Example:

package main

import "fmt"

func main() {
    test[int]() // true
    test[*int]() // true
}

func zero[T comparable]() (res T) {
    return res
}

func test[T comparable]() (res T) {
    fmt.Println(zero[T]() == res)

    return res
}
DmitriyMV commented 1 year ago

Example:

package main

import "fmt"

func main() {
  test[int]()
  test[*int]()
}

func zero[T comparable]() (res T) {
  return res
}

func test[T comparable]() (res T) {
  fmt.Println(zero[T]() == res)

  return res
}

This example doesn't work for type mystruct struct{ slc []int } - it will simply not compile. The proposal does.

Merovius commented 1 year ago

@Nasfame For your question about var z1 T = zero: I assume the spec-change will do the same as for comparison against nil: The spec will talk about one of the values in a comparison being "the predeclared identifier zero" and allow that. Consequently it will reject your code, as it doesn't use the predeclared identifier zero. That is the same as this code being rejected currently:

func main() {
    var f func() = nil
    fmt.Println(f == nil) // works
    fmt.Println(f == f) // doesn't work
}

@aohorodnyk

I thing the question should be focused on top of this part of the proposal

I'd refer to what I said here: The proposal addresses multiple things with a single mechanism, none of which is the "primary" or "only" thing it does. I think it's important to acknowledge that and that an alternative that only solves one of them - no matter which one it is - has this disadvantage over the proposal.

That being said, I also disagree that your code

func zero[T any]() (v T) { return }

really does replicate this part of the proposal. In particular: In the language as it is today, the type argument for zero is not being inferred. So you'd have to write return zero[time.Time](), err, which is worse than return time.Time{}, err.

Of course we might at some point change the language to make type inference more powerful and infer type arguments from assignment context. At that point, yes, that function would replicate that part of the proposal.

Though note that you'd still be stuck on having to declare func zero[T any]() somewhere. If it is a predeclared function, then what's the benefit over the proposal? If it is in the standard library, users would have to write return somepkg.Zero(), err, which is again as clunky as return time.Time{}, err. If it isn't defined anywhere, users would have to declare it themselves, which is boilerplate.

Note that the primary argument behind the predeclared min/max functions was that they are commonly used, so shouldn't require an import. This applies even more so to zero, I'd argue.

So, in summary

  1. Your suggestion does only replace part of this proposal.
  2. It does require another language change (type inference) to even do that.
  3. It then still needs to be defined somewhere, which leaves us no better, or worse, off than this proposal.
Merovius commented 1 year ago

@Nasfame As I said above, the likely way this would be put into spec is "as a special case, any value may be compared against the predeclared identifier zero and evaluates to true if it is the zero value of its type". This is analogous to how nil works today:

Slice, map, and function types are not comparable. However, as a special case, a slice, map, or function value may be compared to the predeclared identifier nil. Comparison of pointer, channel, and interface values to nil is also allowed and follows from the general rules above.

func main() {
    var f func() = nil
    fmt.Println(f == nil) // works
    fmt.Println(f == f) // doesn't work
}
merykitty commented 1 year ago

@Nasfame x == zero does not resolve to x == MyStruct{}

Merovius commented 1 year ago

@Nasfame I disagree with the distinction you are making. In particular, your assertion that x == zero "resolves to x == mystruct{} is directly counter to what I said above how that comparison would likely be defined in the spec - which is exactly what x == nil does.

Either both "resolve to a value", or neither does. There is no basis for the distinction that you are making, except that you choose to think about it that way.

I don't think it is helpful to further discuss this, so I at least will duck out of this particular thread of discussion.

merykitty commented 1 year ago

@Nasfame var x MyStruct = zero and var x MyStruct = MyStruct{} are 2 different ways to do the same thing, they do not necessarily resolve to each other.

Merovius commented 1 year ago

zero - can be treated as a untyped constant.

No, it can not. The proposal explicitly says so. You can not write 1+zero and you can not write const x = zero. zero is a special predeclared identifier, just like nil.

If we permit scenario-2, what exactly is zero then?

The predeclared identifier zero. What is nil if you write f == nil? It can't be a function variable with value nil, as function variables are not comparable. You wouldn't say that "in f == nil, nil is (func())(nil), so the comparison should be invalid". So stop trying to argue that in x == zero, zero should be mystruct{}, so the comparison should be invalid.

Please try to understand how zero and nil are similar, instead of trying to force them to be understood to be different. The differences you are trying to ascribe to them are directly contradicting the intended semantics of the proposal.

If that's the case := var x *MyStruct=zero; x==x should be possible

var x func() = nil; x == x is not valid. So neither should this be.

Personally, I didn't find nil spec as complicated as this!

That is because you are trying to make zero more complicated than it is. For reasons that are beyond me.

rsc commented 1 year ago

There is some speculation about what the spec would say. As noted in the top message, the proposed spec changes are at https://go.dev/cl/509995.


There is also some confusion about zero vs nil. To try to be very clear, zero would be exactly like nil except that it can be used to denote and compare for the zero value of any type, whereas nil can only be used for chan/func/interface/map/slice values. The relevant parts of the updated spec say:

In "Assignability", the text would read:

A value x of type V is assignable to a variable of type T ("x is assignable to T") if one of the following conditions applies:

  • ...
  • x is the predeclared identifier nil and T is a pointer, function, slice, map, channel, or interface type, but not a type parameter.
  • x is the predeclared identifier zero.

and

Additionally, if x's type V or T are type parameters, x is assignable to a variable of type T if one of the following conditions applies:

  • x is the predeclared identifier nil, T is a type parameter, and x is assignable to each type in T's type set.
  • x is the predeclared identifier zero.
  • ...

In "Comparison operators", the text would read:

Slice, map, and function types are not comparable. However, as a special case, a slice, map, or function value may be compared to the predeclared identifier nil. Comparison of pointer, channel, and interface values to nil is also allowed and follows from the general rules above.

As a more general special case, any value may be compared to the predeclared identifier zero.

  • For numeric types, comparison to zero is equivalent to comparison with 0.
  • For string types, comparison with zero is equivalent to comparison with the empty string.
  • For slice, map, function, pointer, channel, and interface types, comparison to zero is equivalent to comparison to nil.
  • For struct types, comparison to zero is equivalent to comparing each struct field to zero.
  • For array types, comparison to zero is equivalent to comparing each array element to zero.

In all three snippets, the nil rule is unchanged from what it is today. I hope it is clear seeing the nil rule and the zero rule next to each other that in all cases the zero rule serves as a more relaxed form of the nil rule.


There is also confusion above about the distinction about *new(T), specifically misreading it as new(T) (note the star in the first one). Zero for a given T would be like *new(T). *new(T) is the clumsy kludge some people use today to the zero value for type T that happens to work for all T. That is, these two are exactly equivalent:

var x = *new(T)
var x T

Note that this is different from var x = new(T), which is always a pointer. The confusion about reading *new(T) as new(T) reinforces my claim that it is clumsy and awkward and we need something better.

ydnar commented 1 year ago

There is also some confusion about zero vs nil. To try to be very clear, zero would be exactly like nil except that it can be used to denote and compare for the zero value of any type, whereas nil can only be used for chan/func/interface/map/slice values.

@rsc given this confusion—in your view, what’s the concise case against just using nil instead of a new predeclared identifier zero?

Thanks!

thepudds commented 1 year ago

Hi @ydnar

@rsc given this confusion—in your view, what’s the concise case against just using nil instead of a new predeclared identifier zero?

If I understand your question, I think a concise response from Russ on that topic is in https://github.com/golang/go/issues/61372#issuecomment-1636929105:

  • Re expanding nil, I think accidents like var x int = nil, or writing f(nil) when f takes an integer, or writing x == nil when x is an integer, or writing *x == nil when x is a *int but *x is an int, are mistakes that are worth continuing to diagnose. So I am not in favor of allowing nil to mean any zero anywhere. In C, NULL is defined literally as 0, and it is easy to make mistakes like that. In Plan 9 C our standard headers said #define nil ((void*)0), and it caught plenty of mistakes where you wrote nil and should have written an integer; NULL would not catch those, and nor would an expanded Go nil. Types are good, and we should continue to take advantage of them.

edit: fixed markdown in the quote that was mangled by my copy/paste.