-
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: add kind-specific nil predeclared identifier constants #22729
Comments
One consequence of this would be that the extremely common |
@cespare Very valid point. |
I'd be happier with something in the middle: only adding |
@bradfitz It sounds like you are suggesting that we move toward a different name for the zero value of an interface type, but keep the general overloading of I don't really see (C++ has a similar confusion in which |
Yes.
I like Seeing lots of |
package main
import (
"fmt"
"io"
)
var empty interface{}
func main() {
var w io.Writer
fmt.Println(w == empty)
} |
Which is actually what the proposal says, but I noticed that only now, mea culpa 👎 |
@ianlancetaylor thank you for raising this issue. I think this proposal adds a lot of complexity to solve a single issue, returning a typed nil. What about just making a typed nil equal to |
That breaks valid code. |
If Instead of changing the name of a zero-valued interface, it would also be possible to change the name of a zero-valued pointer. |
I don’t doubt that, but the situation now is equally error prone. Anyway, this was always in the context of a Go 2 major version bump.
… On 15 Nov 2017, at 09:32, Brad Fitzpatrick ***@***.***> wrote:
What about just making a typed nil equal to nil?
That breaks valid code.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
|
This issue of I think it's time to reconsider what the real issue is. I personally think the current implementation is sound, useful, and when someone understands how it works, they're not going to make any mistakes with it. Not any more than with any other feature. Maybe the only real problem is education around the issue. People come to Go with expectations. And, when they are not explicitly and repeatedly told about this behavior, they assume it works differently than it actually does. But, that doesn't mean it works bad, it just means that people's expectations are bad. Maybe the only thing we need to fix is people's expectations. |
Perhaps the problem is less interface equality and more the ease of accidentally assigning a nil pointer to an interface value when you didn't intend to. |
@davecheney Even for Go 2, I don't think it's OK to remove the ability to test whether an interface is the zero value for interfaces. @faiface You're not wrong, but if people consistently get something wrong it's worth exploring ways to change it to make it less error prone. @neild suggested in conversation that we should simply change the zero value of pointers to |
I'm concerned that this would just push the misunderstanding around while breaking everyone else's muscle memory—but another alternative: Accept #19642 for making All the This could of course expand the problem by allowing one to think it possible to see if whatever is in the interface is zero, but that would come with allowing a universal zero value anyway. |
I'm not a fan of introducing so much nomenclature (nilptr, nilmap, etc.) for relatively little benefit. I also think that we should keep nil as the zero value for pointers, it seems to me that nil is more commonly used with pointers than interfaces (to be verified). It also would be a mistake to equate a test of (interface) x == nil as a test that ignores typed nils (as @davecheney suggested). @bradfitz already pointed out that it would not be backward-compatible. But it's actually important that we have this facility in the first place: A common idiom in Go is to define data types whose zero values are values that are ready to use. It can make a lot of sense to define a pointer type whose zero value (nil) is a ready-to use value. It may have methods that work with nil receivers. Such a type may even implement the error interface and thus represent a valid error (and we might not know about it if such a type is defined in some external library). It is imperative that in such a case the test err == nil doesn't yield true. Perhaps far-fetched a scenario for an error type, but not so much for other situations. But there is another side to this problem, and that is that there is no easy way (*) to test if a variable of interface type holds a (typed) nil pointer (or map, or channel) without knowing the actual type. (This does come up, e.g., in iterators walking over graphs such as the Go syntax tree in go/ast.) If the language had a facility to test for such non-nil interfaces x holding nil values (perhaps via a builtin function isnilptr(x)), presented squarely next to the x == nil test, as a programmer one might be more acutely aware of the two possibilities and automatically think twice. (*) One way of doing it is using reflection
|
@griesemer why single out pointer-y things for such a builtin? Why not a more general
(I wouldn't be surprised if that's missing some edge cases) In either case, if it were a builtin it should fail to compile unless it's parameter is an interface, to avoid confusion like |
Why single out testing for the presence of a zero value? An interface type specifies the desired properties of implementations of that interface; if zero-ness is a significant property of an interface, then shouldn't it be part of the type definition? type T interface {
IsZero() bool
} It feels to me that interface equality is approaching the problem from the wrong direction; I think that in cases where someone is trying to test for a typed nil value in an interface they almost always really wanted an untyped nil interface value instead. e.g., problem the nil error FAQ entry describes is not that |
@jimmyfrasche Maybe. I chose the pointer case because this is the one that I've actually seen in practice. @neild You have a point with using a method. |
I love this proposal. I think it's worth considering |
What about making nil a variable OR a function that has a context i.e.
This way, there's no new keyword, but we expand the use of the nil keyword (like we expanded the use of make). If we combine this with a builtin iszero(x) function where x is any value, and accompanying reflect.IsZero(reflect.Value) function, we should capture most of the use cases without introducing many new keywords. |
@ugorji Just for the record: |
I would like to point out one thing. Having This is not how it happens in Go. Let's take a look at numbers for a similar example: var x interface{}
x = float64(0)
if x == 0 {
fmt.Println("this doesn't happen!")
} Did you guess correctly what happens in the above example? It is similarly counter-intuitive at first, but it makes sense. The With interfaces it's similar. Does it even make any sense whatsoever, to ask whether an interface value is a nil value of an arbitrary pointer type? |
@faiface I agree: it doesn't. But the evidence is clear that that is what many people new to Go think is happening. It's a FAQ, and it's visibly a stumbling block for new Go programmers. I think it's worth examining whether there is something we can do to cause fewer people to stumble here. |
@ianlancetaylor How about the numbers? Would you solve those too? If not, being able to check if interface contains arbitrarily typed nil pointer, but not being able to check if it contains an arbitrarily typed zero, or one, would be an inconsistency that could cause more confusion than clarification. |
@faiface This proposal would not fix the comparisons of an interface value with (Note that this specific proposal is not for a way to check whether an interface contains a nil pointer of arbitrary type. I mentioned that as "something to consider," but it is not part of the proposal.) |
@alanfo You seem convinced that interface nil vs. pointer nil is just a matter of teaching and better understanding of the language. I don't agree with that. I rather think it's objectively confusing. Consider: n = nil
e = n
if e != nil {
fmt.Println(e)
} This code fragment prints The problem is that interface nil and pointer nil are conceptually two vastly different concepts, and it was not a good idea in the original design of Go to give them the same name. I believe this particular problem should not anymore be the rite of passage for Go programmers that it currently is, because it's unnecessary and doesn't serve a purpose. (Backwards compatibility is not a particularly convincing purpose in a language that is currently seeing some dramatic changes anyway.) Just my 0.02€... ;) Pascal |
They are different but presumably, back in v1.0 days, the Go team didn't consider they were different enough to justify the use of another predefined identifier (such as Although we are likely to see a dramatic change to the language in the introduction of generics (but probably not now error handling) the current generics proposal is still backwards compatible (or, at any rate, can be) and I know the Go team regard this as a very important consideration. As I don't really see how anyone can use interfaces successfully without being able to distinguish between an interface variable and what (if anything) it currently contains, then education must play an important part and, whilst I don't object to a language change being made to reinforce this, I do think such a change should be simple, proportionate and (preferably) backwards compatible. |
This proposal has a series of five steps aimed to fix the problem over time. Now maybe it's a bad proposal--in fact, it probably is--but it's more than just one idea. |
When I referred to your 'alternative idea' I was alluding to your final paragraph:
As you made clear, this wasn't part of the actual proposal but an idea to consider. |
Ah, sorry. |
This is a little off-topic, but since https://golang.org/doc/faq#nil_error was mentioned I feel it's not precise enough:
It seems that the bad (first) example is "use(ing) the error type in their signature" as advised, yet still broken. It should not only use the error type but also return the nil interface instead of a differently-typed nil (if that's the correct wording). |
@griesemer wrote:
I've been thinking about this for a long time, and while I buy the theoretical argument, I'm still not entirely convinced. One point in favour of disallowing typed nils as valid receivers: the pointer method set also contains the value method set, but if you happen to call a value method when the pointer is nil, you'll get a panic, so that property isn't as useful as it could be. Passing the zero value is one possible way of fixing that, but disallowing nil receivers would be another. If you disallow nil receivers, then perhaps it's reasonable to change a typed nil pointer into an untyped nil when converting to an interface. The problem is much less acute for slices, channels and maps, so perhaps they could be left as is. But as @ianlancetaylor points out in the above-mentioned issue, doing this would change the behaviour of existing code, which probably rules it out as a language change. |
Would completely disallowing a naked (uncasted) Sorry, I don't remember if it was suggested and declined as "too much existing code affected". |
@tandr Anything that requires us to rewrite |
@ianlancetaylor You could have |
The options are:
Whichever is done, for backwards compatibility reasons p == nil and err == nil can probably never be changed, but you could add a lint check + rewriter to used the preferred one. On identifiers, my two cents are that |
To pick up thw comment by @tandr: why to we need the ability to invoke methods on a nil value at all? I think I understand why its technically possible and permitted by the language as-is, but is it necessary for any purpose? |
@andig It’s useful for exactly the same reason that dynamic dispatch is useful in other cases: You can implement the |
I did not read all comments in this very long topic, sorry for that. But is that the same story as Dart? Dart 1: no null-safety
Why don't we apply this successful story to Go? |
@xuan-nguyen-swe This issue doesn't have anything to do with null safety. That is a completely different topic. For null safety see, for example, #28133. |
@ianlancetaylor thanks for redirecting me to the correct topic! |
A common error for people new to Go is misunderstanding that a interface that is not
nil
can contain anil
pointer. This is one of the most commonly cited entries in the Go FAQ: https://golang.org/doc/faq#nil_error. A quick search shows at least 24 threads on golang-nuts discussing this, even though it is already in the FAQ.It is not new to observe that one of the causes of this common mistake is that
nil
is overloaded. Since changing that would not be backwards compatible, I propose the following changes.We add six new predefined identifiers:
nilptr
,nilinterface
,nilslice
,nilchan
,nilfunc
,nilmap
. These new identifiers are untyped constants that only resolve to a certain kind, much as1.1
is an untyped constant that only resolves to afloat
orcomplex
type.nilptr
may be used as the zero value for any pointer type,nilinterface
may be used as the zero value for any interface type, and so forth. An attempt to use, for example,nilptr
in an assignment or comparison with a variable or value that is not a pointer type is invalid.We add a vet check that warns about any comparison of a value of any type with plain
nil
. We encourage people to changex == nil
tox == nilinterface
orx == nilptr
or whatever is appropriate. Since initially this vet check will trigger all the time, we can not turn it on when runninggo test
. It could be on by default when runninggo vet
.At this point people who run
go vet
will no longer make the common mistake. Ifv
is a value of interface type, then writingv == nilptr
will be a compilation error. Writingv == nilinterface
will not cause people to erroneously think that this is testing whetherv
contains anil
pointer.In some later release, we turn on the vet check when running
go test
.If we are ever willing to make a backward incompatible change, we can make
v == nil
a compilation error rather than simply being a vet error.Something to consider is that one could imagine permitting
v == nilptr
when v has an interface type, and having this betrue
ifv
is notnilinterface
, ifv
holds a value of pointer type, and if the pointer value isnilptr
. I don't know that this is a good idea, and I'm not proposing it. I'm only proposing the above.The text was updated successfully, but these errors were encountered: