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
spec: add untyped builtin zero #61372
Comments
Change https://go.dev/cl/509995 mentions this issue: |
Assuming that you prefer I personally find |
I would really like for the spec and builtin change to include guidance on this. That is, I assume we want "idiomatic Go" to not replace most uses of We really want to discourage rewriting |
An alternative is to adding |
FWIW, I would still prefer #21182 here ( |
I am not a super smart guy but, I do think explaining Secondly, I think standard cmp package shouldn't be reasoning behind any change because we as developers now going to bump into same package names with 1.21 (because we use google/go-cmp primarly for our testing and IDEs will show up 2 results now, did you mean this or that etc), I am personally not happy with 1.21's cmp package direction Thirdly, I think explaining zero as a concept over struct{} will be harder for everyone and in most cases, we never return allocated struct and error at the same time, it should be the dev who is assigning to a default when error occurs, not the other way around. I always never liked returning apologies for the noise from me |
I prefer |
FWIW I don't have strong opinions about how to spell a universal zero value - I'm fine with any color for that bikeshed.
The justification isn't the The I think it's fair to criticize arguments like the explainability or readability of |
this proposal would also satisfy #26842. @rsc would accepting this proposal also involve changing cmp.Or to accept any type and use @josharian #21182 could be additionally accepted. It would be less needed than it is now but there's still an argument to its utility and if it were accepted instead of this proposal there'd still be a need for the additional functionality contained in this proposal. |
I like this a lot. I also like What I almost want is for What I really want is "zero is a universal zero that you can use except when you know you are thinking about a thing in pointer terms, in which case you want nil". Although thinking about it more, at least two cases where I currently use nil (slices, maps), I think "zero" would be comparably/similarly expressive. I am now very conflicted on whether I think it's more consistent to call the zero value for a slice |
Okay but thinking about it more, I have concluded: I would also be fine with just extending Observation: That you can use |
Could it be just 0 instead of an identifier? ( |
@seebs I think it would be interesting to allow |
A linter to complain about |
|
It might be funny business if |
Why won't What guidance do you propose to give for when 0 vs. zero and nil vs. zero should be used? In other words, what would be idiomatic? For example, for I'm concerned by the direction the language is evolving. I see non-orthogonal features like this being added instead of existing features being generalized. There is already a zero value in Go: nil. If you lump all the built-in number types together, most built-in types have a nil value. Numbers, strings, arrays, and structs are the exception. Instead of adding something new to solve this one problem, let's generalize what we already have: make nil work for all built-in types. This has been proposed many times by many people. I'm disappointed that this proposal didn't address why this obvious solution won't work. My vote is no until it's changed to do so.
As an aside, I don't agree that coming up with a good name is bikeshedding. Naming is often characterized as one of the two hard things in computer science. Whether or not you agree with that quotation, naming is indeed important, and entirely relevant to the quality of a software design, as opposed to a nuclear power plant design committee getting derailed by the paint color for a bike shed. |
I would write |
Currently, it works because |
I feel like |
Some have favored removing the restrictions on So often have I refactored |
FWIW there is more to the bikeshed analogy than just the importance of the question. But, in any case, I was merely trying to express that it doesn't matter to me. I want something to happen and I find |
So, I think the messiest aspect of this is roughly the behavior in ambiguous contexts. If you're returning an interface value, then I could see a hypothetical benefit to a Right now, if you are actually returning an interface type, and you have a lot of So I think |
Returning |
The only thing that is not possible today is the zero value comparison, at least not without To create a generic zero value That's why I'm in favor of an |
The obvious follow-up question is how often people define a helper variable which is known to be the zero value to compare against - so that they don't have to write the comparison for empty structs. That is "comparison against the zero value is rarely used" is but one explanation for why this comparison doesn't appear very often. The same logic applies for your grepping for returns of zero struct types. That being said, I have no idea how to find that and it would obviously be preferable to have evidence that this is a problem worth solving. |
I agree that there could be a lot of missing zeroed structs initialized with
The original questions of my first comment stay unanswered and redundant notation will be a problem with |
In my opinion, both are "right". I expect that over time, a style will emerge and I would predict that it is to prefer using
I expect I would personally use
What I sketched above is what I use today for
That is de-facto what is happening today. Personally, I don't like to use empty structs in cases where the literal can not have fields set (e.g. But also, struct literals do not address the comparison aspect. Note that this has been discussed above, in the context of the proposal to expand type-inference so you can simply type FWIW these are opinions. I expect other people have other opinions. Which is why I didn't try to answer them initially- I don't believe there is or needs to be a consensus answer to them. |
In addition to what already discussed about comparing structures, there are situations when we had to add a method to check if a value is zero because the alternative was kind of a hassle to write and read. For example, even though it's possible to write If we could have written
I don't consider it inherently negative to have multiple ways of expressing the same concept. For example, if In a recent release of Go, the notation Similarly, we have both So, I believe that rejecting a proposed language change solely because it introduces an alternative way of expressing something that's already possible is not sufficient to reject the proposal. |
@gazerro one caveat is that it can be confusing even for a computer. For instance when inferring types, 0 doesn't really say whether it should be const int or const float, for instance. We have to rely on other heuristics. :) |
Again from a minimal language design perspective these concepts overlap. I don't want to ask myself or my coworker which style to use. It is a hidden feature Go to have great readability because of low personal style. This helps also the readability aware writer.
This implicit personal style is logic but not simple.
You need to differentiate if you use it in a "People dislike the extra typing" is from the writers' perspective. The reader doesn't care if you needed 1-2 seconds more to write something. The reader wants consistent code and have as little possible code constructions as mental load.
Multiple ways expressing the same concept harms readability systemically. People will use every possible way of expression and mental load for the reader increases.
Having different writing styles for a number format is a different league and I also think it improves readability 👍🏼
I don't want to reject this proposal. At the end is okay for me to introduce |
One other little worry: I've seen people adding []struct{} fields to structs in order to actually make sure that some types where not comparable. Often to make sure they couldn't be used as map keys. It's probably not a huge concern at all but in theory, making every struct type comparable to their zero value could be a breaking change for interface comparisons. https://go.dev/play/p/eAb_Z6hSOYu Instead of recovering a panic, that would just follow the true branch. Again it's perhaps a bit farfetched, perhaps not. Just wondering if people might have relied on this. |
@meyermarcel I think this is moving in circles. These arguments have been known when the proposal was marked accepted.
What does this mean for this issue, concretely? Note that there is very little we can do to influence how people use the language. We can add "Having a discussion" is a process, not an outcome. It's also not something that should or need to happen here - it can happen (and has historically happened) in blogs, on mailing lists, in slack, on reddit… In the marketplace of ideas, so to speak. So what do you imagine the outcome of continuing to argue on this issue to be? |
The proposal is/would be not to make every struct type "comparable to their zero value". It is to make them comparable "to the predeclared identifier Again, this is equivalent to how |
@Merovius it is still unclear to me as zero stands for the zero value. zero itself is just the identifier. Nowadays, at compile time, incomparable struct types comparison to their zero fail at compile time and panics at run-time in interfaces. That behavior would change. Again that might be far-fetched. I believe people started doing that with the introduction of generics. |
@atdiar This has already been discussed. I'm not speculating - it works like nil works today. That analogy answers whatever questions you might have. If you understand the language today, then you'll understand the language with |
@Merovius then it is a backward incompatible change in absolute and my question still stands. Edit: I stand corrected on that. It's not a backward incompatible but then introduces some kind of inconsistency. |
@atdiar |
@atdiar Please read the comment I linked to and the discussion leading up to it. Your questions have been answered. And no, it is simply untrue that there is any compatibility problem. |
@Merovius @merrykitty I'm not sure I like that too much. For nil, it wasn't an issue since there was only one way to write the zero value. For instance []int zero value can only be written nil. https://go.dev/play/p/SA8FDVPZbwF I find it a bit odd. But alas. |
You are correct that that's how it would work. You are not quite correct that this isn't a thing with |
You're right. At the same time, even in the spec, the zero value is nil for this type. Not even typed nil although it probably is. No one really uses a type conversion. Still, in usage, we already have many zero values for structs and zero is actually not an identifier for them. But in that case, I'd really rather have a predicate or something else. It's a bit confusing. |
This discussion has made clear that we're not ready for this change. Retracting the proposal. |
@rsc wait, can you elaborate a bit? You said in the last message that "No new information has been presented since the proposal was accepted.". And now it suddenly closed and retracted. What changed since then? |
The new information was the sheer lingering of this thread. I'm sad about the decision because I liked zero, but I can't fault the reasoning. |
If we apply the same reasoning to the generic proposal, we would never got generics. As well as This proposal had overwhelming support, few edge cases, it pas properly discussed and accepted. It is Russ right to retract it, because it's his proposal. But I'm going to admit that closing and retracting things like that, without any explanation, is demotivating for all who participated in discussion. |
I don't think that the comparison with generics is quite right. There were several public proposals about generics that had a great deal of discussion and were then withdrawn and reconsidered. It's true that they weren't formally accepted, but the general idea was the same: we didn't move forward with generics until there was broad (though not universal) agreement. And I wouldn't say that this proposal was withdrawn without explanation. The explanation was that many people still disagree with the proposal, as can be seen in the discussion on this issue. The acceptance may have been premature, or the acceptance may have led people to think further about it and led them to disagree. |
With all due respect, that would not be the first accepted proposal where many people disagreed.
And that contradicts @rsc notes about "Reconsideration". That also means that with sufficient repeating of the same points from the same people any, even accepted, proposal can be declined in the end. What I'm trying to say, it's IMHO wrong to say "we are not going to reconsider our decision until new data arrives" and then reconsider it without any sort of explanation about what sort of new data arrived. That also kills any initiative to participate in any proposal discussion since rules of accepting/declining/reverting are arbitrary and can change at any point. P.S. With this and #62487 both retracted, probably some Google internal discussion happened which lead to this? And if so, why not share the summarized details? |
The goal of the proposal process, as described at https://go.dev/s/proposal, is to reach consensus. If the proposal process is accepting ideas where many people disagree, then I hope that many more people agree, or at least that it is clear that the proposal is important for some other reason. Are there specific proposal that you have in mind?
Respectfully, more than one person has explained what the new data was. It's true that the proposal committee (which is not only Googlers) and others discussed this issue off line. That discussion amounted to: we have not reached a consensus here. And the evidence for that was the continued discussion on this issue. I'm sorry that you feel that this kills any initiative to participate in any proposal discussion. I hope that most people don't feel that way. |
I think the main source of confusion was really just that @rsc announced its withdrawal kind of suddenly. If he had just had an extra few words in there, even something like
it wouldn't have been as confusing. |
Then why close the discussion instead of just retracting the acceptance and saying where we're at and where we go from here? It seems like you're saying the approach in this thread is now off the table entirely. |
Personally I don't think this approach is off the table. I do think that we need to step back and reconsider the problem. We know from experience that proposal issues are not good places to discuss a problem. This issue already has over 300 comments. Keeping this issue open is not a useful path forward. |
And that what exactly is missing. Just like @DeedleFake said - a few phrases explaining, that, despite proposal being accepted, consensus was not reached neither here nor in committee internally and because there is no clear part forward, proposal is un-accepted and closed for a time being. Small explanation is all that needed. The reason for this need is also quite simple - Russ made a statement that merely disagreeing or continued discussion of the same points on accepted proposal is not enough to reverse it. But then suddenly it is.
#59488 (comment) comes to mind, where there was no agreement about |
I propose to add a new predeclared identifier
zero
that is an untyped zero value. Whilenil
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
mimicnil
with fewer restrictions:zero
is assignable to any variable of any type T that does not already have a short zero (0, "", nil), including when T is a type parameter with constraint any.zero
when it cannot already be compared to a short zero (0, "", nil), again including when T is a type parameter with constraint any.That's it. That's all the rules.
Note that assignability includes function arguments and return values:
f(zero)
andreturn zero, err
are valid.See CL 509995 for exact spec changes.
This proposal addresses at least three important needs:
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.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 incmp.Or
(cmp: add Or #60204).Shortening error returns:
return zero, err
is nicer thanreturn 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:
iszero(x)
. This proposal usesx == zero
instead.zero(T)
is a zero of type T. This proposal uses plainzero
which enables use in comparison and avoids a usually unnecessary(T)
. In non-assignment, non-conversion contexts where a type must be written, this proposal usesT(zero)
, which seems more idiomatic to me at least.{}
. Later discussion in also consideredzero
._
.The text was updated successfully, but these errors were encountered: