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: builtin: zero function #60695

Closed
nathan-cormier opened this issue Jun 8, 2023 · 23 comments
Closed

proposal: builtin: zero function #60695

nathan-cormier opened this issue Jun 8, 2023 · 23 comments
Labels
Milestone

Comments

@nathan-cormier
Copy link

Presently the language offers a few ways to get the Zero Value of a type, but it would make code more readable to have a canonical way to accomplish this.

This proposal would like to add a new builtin function:

func zero(Type) Type

This is quite similar to the new builtin, but returns an instance of Type instead of a Pointer-To-Type.

This would serve as shorthand for some of the many ways to get a zero value. For example one could do:

return zero(MyType)

Instead of:

var z MyType
return z

Or instead of:

return *new(MyType)

It also makes comparison with zero values rather concise and self-documenting:

if val == zero(MyType) {

This is more readable than either of these existing tests:

// this requires using the reflect package
if reflect.ValueOf(val).IsZero() {
// this means the same but doesn't signal intent as clearly
if val == *new(MyType) {

And of course, just like new this would play nicely with generics:

func example[T any]() T {
  // do something that returns an error...
  if err != nil {
    return zero(T)
  }
  //...
}
@gopherbot gopherbot added this to the Proposal milestone Jun 8, 2023
@zephyrtronium
Copy link
Contributor

Closely related to #35966, which has been duplicated a few times. I don't think a built-in function specifically rather than a nil-like identifier is among its duplicates, though.

@ianlancetaylor
Copy link
Contributor

Of course it's easy to write this yourself.

func Zero[T any]() T {
    var v T
    return v
}

Not clear that this comes up often enough to add a built-in language function.

@thediveo
Copy link

thediveo commented Jun 9, 2023

Presently the language offers a few ways to get the Zero Value of a type, but it would make code more readable to have a canonical way to accomplish this.

This proposal immediately reminds me of the "standards" XKCD comic https://xkcd.com/927/, just replace standard with canonical.

@nathan-cormier
Copy link
Author

Of course it's easy to write this yourself.

func Zero[T any]() T {
    var v T
    return v
}

Not clear that this comes up often enough to add a built-in language function.

Couldn't you say the same thing about new as well? Why does the language have new if you can easily do:

func New[T any]() *T {
	var v T
	return &v
}

Personally I've seen Zero Values come up often enough, they are a fundamental concept in the language.

@nathan-cormier
Copy link
Author

This proposal immediately reminds me of the "standards" XKCD comic https://xkcd.com/927/, just replace standard with canonical.

For those that value readability, I don't think there's any disagreement that zero(MyType) signals intent more clearly than *new(MyType). If we never pushed for better standards in languages, we'd just be stuck. All that would be needed is zero to be recommended by style guides / linters and you wouldn't see the other ways of creating zero values much over time.

@thediveo
Copy link

thediveo commented Jun 9, 2023

I've used the var zero T pattern in the cases I had need for a zero value and don't consider it to be cumbersome. I've learned in my four decades of programming that it doesn't help when everyone gets his favorite syntax as then people don't understand each other's code. Does zero(x) zero out an existing value? Ah, it's taking a T, not an x. That's the reason why I never use new, more so as it doesn't necessarily allocate on the heap and it deviates in its type arg usage.

@earthboundkid
Copy link
Contributor

I think this issue is a duplicate and should be closed. There is a slight difference between a built-in universal zero value and a built-in universal zero function, but the difference can be hashed out in #35966. FWIW, I think a function is strictly inferior to a value because it can't be used for #26842.

@ianlancetaylor
Copy link
Contributor

@nathan-cormier

Couldn't you say the same thing about new as well?

  1. The new built-in of course preceded generics, and could not have been written as easily without generics. And we can't remove it now because of compatibility.
  2. I said "Not clear that this comes up often enough" about zero, but I think there is good reason to believe that for new it does come up often enough to justify a built-in.

@apparentlymart
Copy link

Anecdotally, I personally have written variants on var T zero; return zero more often than I have ever used the built-in new function, despite the new function being with us since before I even started writing Go (circa 2015) and this zero value pattern only really becoming useful since Go 1.18 introduced generics. (I tend to use composite literals to write out pointers to new struct values, and variants on var T whatever; return &whatever in the very rare situations where I return pointers to non-struct values.)

I do of course accept that new might be considered a historical mistake and wouldn't be included in the language if proposed anew today due to being implementable with generics... that's very reasonable. But if the argument is that the need for new comes up more often than zero, then I can say that this does not match my experience, though of course I might be unusual.

I would personally prefer a predeclared identifier similar to nil that is assignable to anything rather than a function that takes a named type, because that would also address the minor annoyance of return zero, fmt.Errorf(...) without having to repeatedly name the return type, when the first return value is not nilable in addition to the new problem of returning from functions with generic return types.

@someview
Copy link

This is often used In generic cases

@dolmen
Copy link
Contributor

dolmen commented Jun 28, 2023

This is often used In generic cases

But still generic code will always stay a niche compared to the mass of Go code. I don't think that the use in generic code would be enough to justify a new builtin. var zero T is a good idiom for that case.

@thediveo
Copy link

This is often used In generic cases

But still generic code will always stay a niche compared to the mass of Go code. I don't think that the use in generic code would be enough to justify a new builtin. var zero T is a good idiom for that case.

More so as the introduction of Generics more or less immediately introduced the var zero T pattern.

@someview
Copy link

But still generic code will always stay a niche compared to the mass of Go code. I don't think that the use in generic code would be enough to justify a new builtin. var zero T is a good idiom for that case.
There are many scenarios for generics, especially when building a project's base library.
I

@apparentlymart
Copy link

apparentlymart commented Jun 30, 2023

@Nasfame I don't think I've yet needed a generic "value of arbitrary type is the zero value of that type" test, but I can see how that might be useful for some things, such as if you're serializing a value and want to skip serializing anything that would just be the zero value anyway, since it's fine to just leave that unpopulated when deserializing. encoding/gob has behavior like that, though last time I looked it was doing the zero check separately for each type and so didn't need a generic definition of "zero"; perhaps it would benefit from one.

I think your function IsZero would effectively embed Zero though, right?

func IsZero[T any](v T) bool {
    var zero T
    return v == zero
}

The above is equivalent to comparing v with the result of Zero, and so Zero seems like the more general of the two.

I'll concede that IsZero has the advantage that you can call it without actually naming the type of v, whereas Zero will always require specifying the type. That disadvantage is part of why I'd favor zero being a keyword similar to nil that can be assigned to a variable of any type, so that one could write v == zero instead of v == Zero[string] when it's clear from context that v is of type string, or when it doesn't actually matter to the reader what type v has.

@earthboundkid
Copy link
Contributor

When cmp.Or is merged, cmp.Or() will be the same as Zero().

@apparentlymart
Copy link

For those who didn't see the other proposal: cmp.Or.

So what @carlmjohnson means, I assume, is that cmp.Or will return the first argument that isn't the zero value, and as a special case when there are no arguments it will return the zero value. Therefore it doubles as a tricky way to obtain the zero value of a given type.

I assume that cmp.Or would also effectively have IsZero embedded in it, since its specified behavior depends on being able to decide that.

@zephyrtronium
Copy link
Contributor

cmp.Or's type parameter has a comparable constraint, so calling it with zero arguments is only the same as the proposed Zero for comparable types.

@rsc
Copy link
Contributor

rsc commented Jul 15, 2023

Thanks for the discussion here. I filed #61372 to try to move a concrete plan forward. In terms of that proposal, you can write zero instead of zero(T) most of the time, and T(zero) when you need an explicit type.

@rsc
Copy link
Contributor

rsc commented Aug 9, 2023

This proposal is a duplicate of a previously discussed proposal, as noted above,
and there is no significant new information to justify reopening the discussion.
The issue has therefore been declined as a duplicate.
— rsc for the proposal review group

@rsc rsc closed this as completed Aug 9, 2023
@nathan-cormier
Copy link
Author

Makes sense, thanks @rsc !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Declined
Development

No branches or pull requests

11 participants