Skip to content

proposal: Go 2: use an identifier other than nil for uninitialized interfaces #24682

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

Closed
pcostanza opened this issue Apr 4, 2018 · 10 comments
Closed
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Milestone

Comments

@pcostanza
Copy link

This is a variation of #21538 with a very important difference (see below). It's also related to #22729 and #24635.

It's confusing that nil can be used both as a value to represent an uninitialized interface, and as a value for the pointer that an interface is initialized with. This is a stumbling block for people new to Go, but also trips up experienced developers every now and then.

Proposal: use a value other than nil for uninitialized interfaces. To avoid adding new pre-defined identifiers, the value could be syntactically represented as {}, for example, but it may also be ok to introduce a new identifier, like null or nilinterface, or some such. This means testing for an uninitialized interface would have to be expressed like this:

var v interface{}
...
if v != {} {
   ...
}

With this proposal, tests against nil (like in v != nil) are not allowed anymore. This proposal explicitly does not suggest to add a way for testing for typed nil pointers stored in interfaces in any way. (If you prefer such a feature, refer to one of the other proposals mentioned above, or submit a separate, potentially complementary proposal.)

Rationale: A lot of people seem to be strongly opposed to adding a way to allow for testing for typed nil pointers being stored in an interface variable other than through the reflect package, and strongly encourage people to call methods instead that are supposed to handle nil pointers gracefully. I still strongly believe the language is improved if interface variables are simply not comparable to the nil identifier anymore at all to make that distinction even more explicit, and ensure that developers don't falsely believe they compared to any other possible meaning of nil.

This proposal implies a change that is not backwards compatible, but it should be easy to write a tool that automatically converts programs since a type checker should be able to detect the relevant cases.

@gopherbot gopherbot added this to the Proposal milestone Apr 4, 2018
@mvdan mvdan added v2 An incompatible library change LanguageChange Suggested changes to the Go language labels Apr 4, 2018
@bcmills
Copy link
Contributor

bcmills commented Apr 4, 2018

Could you clarify how this proposal differs from #22729?
It seems like pretty much a strict subset, albeit with nilinterface renamed to {}.

Note in particular point (5) of @ianlancetaylor's proposal:

  1. 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.

@pcostanza
Copy link
Author

Yes, it’s a strict subset. Considering that the other proposals all gained criticisms for aspects that go beyond this particular subset, I believe it’s better to propose a minimal version that focuses on the IMHO essential part.

@bcmills bcmills changed the title Proposal: use an identifier other than nil for uninitialized interfaces proposal: use an identifier other than nil for uninitialized interfaces Apr 4, 2018
@bcmills bcmills added the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Apr 4, 2018
@bcmills bcmills changed the title proposal: use an identifier other than nil for uninitialized interfaces proposal: Go 2: use an identifier other than nil for uninitialized interfaces Apr 4, 2018
@pcostanza
Copy link
Author

Here is an attempt at showing that this proposal objectively improves the language.

Consider the following code fragment:

v = nil
w = v
if w == nil {
   fmt.Println("no")
} else {
   fmt.Println("yes")
}

Without the proposal above, this code fragment is ambiguous: You need to know the type of the variable w to determine whether this code fragment will print no or yes. If w is an interface type, it will print yes, if w is not an interface type (for example, some pointer type), it will print no.

With the proposal above, variables of interface types are not allowed to be compared to nil. Then you have two possible cases:

v = nil
w = v
if w == nil {
   fmt.Println("no")
} else {
   fmt.Println("yes")
}

Here, w must be a variable of a non-interface type, because only such a variable can be compared to nil, so the fragment will print no.

Alternatively:

v = nil
w = v
if w == {} {  // or null, or nilinterface, etc.
   fmt.Println("no")
} else {
   fmt.Println("yes")
}

Here, w must be a variable of an interface type, because only such a variable can be compared to {}, so the fragment will print yes. In both cases, there is no need for looking up the types of the variables involved to determine the outcome.

I'm not arguing that this particular example occurs often (it's artificial, so it probably doesn't). I'm also not arguing that it's hard to find out what the types of variables are (with a good IDE probably just hovering the mouse cursor over the variable name, without a good IDE scrolling to some other place in the code). I'm nevertheless arguing that this is nevertheless an improvement, and that in fact Go should have been designed like this from the beginning. I also believe that the change shouldn't cost much, because the compiler has to know about the different roles of nil already anyway, and it should also be trivial for a tool like go fix to make the necessary changes to existing source code. I believe the cost/benefit ratio is really good here. ;-)

@pcostanza
Copy link
Author

#24635 is a related but completely different and orthogonal issue. #21538 combines the proposal discussed here and #24635 (which is unnecessary, because they address different issues, so it's good that #21538 was closed).

#22729 goes a lot further than the proposal discussed here. As with #21538, it combines solutions to related, but essentially different problems, IMHO.

@rsc rsc removed the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Apr 9, 2018
@ianlancetaylor
Copy link
Contributor

I can't say that I am sanguine about {}. People will have to write code like v = {} and F({}). Code like this becomes valid Go:

if a, b = F(), {}; a != b {}

We will see a great deal of code like

if err != {} {
    return fmt.Errorf("X failed: %v", err)
}

It doesn't correspond to anything else in the language. I think a predeclared identifier is more feasible, but as the discussion in #22729 shows picking that identifier is not straightforward.

Separately I note that even though Go 2 can break language compatibility, it must still be largely backward compatible. Requiring every line of the form

if err != nil {

to change would be quite drastic. I think it's infeasible without a transition plan.

@pcostanza
Copy link
Author

pcostanza commented Apr 13, 2018

This proposal is not about syntactic choices. It doesn't have to be {}, it can be null, or nilinterface, or empty, or something else.

@ianlancetaylor
Copy link
Contributor

We can't just wave away the choice of identifier. This is going to appear a lot in Go code. The name matters.

if err != null {

if err != nilinterface {

if err != empty {

@bcmills
Copy link
Contributor

bcmills commented Apr 13, 2018

{} does have a nice symmetry to #12854, although I suppose it introduces some ambiguity as to whether the zero interface{} value is equal to struct{}{}.

(An interesting thought experiment: what if they were equal?)

@pcostanza
Copy link
Author

About the syntax: I agree by now that {} is a poor choice, because it doesn't pass the grepability test. I also don't like null because it is too similar to nil and typically means the same as nil in other languages. It will be too easy for users to be confused about such a subtle name change. I also don't like nilinterface because it's too long.

My preference by now would be empty because it's very different from nil, which I believe is warranted also from the semantics perspective. It's also telling, IMHO, that in the source code for Go's runtime library, for example, the concept is being referred to as 'empty interfaces', which suggests to me that empty is a good choice.

About a transition plan: I don't believe the change is that drastic. With this particular proposal (24682), nil cannot be used anymore at all in conjunction with interface variables, neither for assignment, nor for comparison. So a type checker will have an easy time spotting the relevant places in code that need to be changed. The compiler can also have a transitional mode, activated by a command line parameter, to ensure that users can make the transition at their own pace. I can imagine that something similar will probably also be needed for other changes in Go 2.

@ianlancetaylor
Copy link
Contributor

This is a subset of #22729, which has plenty of discussion. Closing this issue in favor of that one.

@golang golang locked and limited conversation to collaborators May 1, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Projects
None yet
Development

No branches or pull requests

6 participants