If only one could go back in time and fix the weird overloading of nil for pointers and interfaces...it is the one breaking change to the Go1 language guarantee that seems worth it.
I don't think it's worth breaking compat, but I agree it would've been nice and saved a ton of confusion.
Right now a pointer to a type that is zero is called "nil". Similarly, an interface that contains zero for both the type and the value is called "nil".
This is really confusing because
var foo SomeType = nil
var bar SomeInterface = foo
fmt.Println(bar == nil) // prints false, which is confusing
If instead you called the zero-value of interface something else, say "unset", it would be a lot less confusing.
var foo SomeType = nil
var bar SomeInterface = foo
fmt.Println(bar == unset) // prints false, which makes sense, because bar is set -- to (nil, SomeType)
I first encountered this idea in this reddit thread:
In Go nil is a special identifier that represents a "zero value" for several types (pointers, interfaces, functions, slices, channels).
"unset" would be a "zero value" for interfaces so that nil could be "interface that has a type but whose value is an zero value of a pointer or function or slice or a channel.
This is inconsistent - now you have 2 identifiers to represent a concept of "zero value".
go has _three_ different concepts: what is now called nil has instances from two classes, let's call them nil.type and nil.interface.
Discussion is about whether it would be better to require programmers to explicitly specify the .type or .interface part in the code they write, or to let the compiler infer it from its arguments (with the special case that it should assume that, in nil == nil, both have the same meaning, so that it evaluates to true)
I think I would favor having two separate names in my source code, with the caveat that the compiler should make it illegal to write x == nil.interface if x is a type and vice versa, but I know too little about go to claim that's really a good idea, and also think it would change if go got generics (and maybe, the code generation thing it has now in place of generics (go generate) already would change my opinion, if I took time to think it over)
yeah, you've exactly identified the confusion -- assigning the zero-value of a pointer to an interface does not give you the zero-value of the interface.
using the same word to represent these two things makes this more confusing, because it violates our intuitive sense of algebraic laws (a = nil, b = a => a == nil); using different words for the zero-value of interface and pointer would break this false intuition (a = nil, b = a =/> a == unset), and save a lot of confusion.
This is because nil is not the same as SomeInterface. They are different types. In the above example, the correct code would be this:
fmt.Println(bar.(SomeType) == nil) // this will print true
You need to explicitly convert the interface to a specific type (as above) before you can compare whether or not it points to a concrete value. Since SomeInterface could point to any type (by implementing all the members of SomeInterface, you can make any type be of SomeInterface). This is because variables that have a interface type are never nil.
I wouldn't call this a design issue, I believe this was done very purposefully and it makes perfect sense to me and other long time Go developers. The biggest problem I've seen with learning Go is for others coming from Ruby / Java / PHP / etc languages, who have this expectation of what "null" is (or nil as the case may be) and what "interfaces" are. The key to learning Go is to come at it with the thinking of C/C++.
Having been at Google when Go was announced internally, I remember being disappointed that a modern statically typed language is repeating Tony Hoare's Billion Dollar Mistake (tm). (I was also disappointed that C interoperability required recompiling your C code using kenc so that it used the Go/Plan9 calling convention. Maybe this has since been fixed.)
I would have expected at least something a maybe/option type (or another mechanism to make optionality and refrences orthogonal concepts), if not full algebraic data types. Allowing Nil everywhere forces lots of otherwise unnecessary Nil checks and introduces myriad opportunities for bugs.
Stepping back and assuming that for whatever reason an ML/Haskel/Swift/Rust/etc.-like maybe/option type is out of the question: when a programmer is comparing a passed interface instance to nil, they're almost certainly trying to answer the question "can I really call the defined interface's methods on this instance?".
Having done very little Go programming, my mental model is that interface pointers are pointers to a tuple of meta-information (including dynamic dispatch table) and a pointer to the concrete type. I can understand that both the meta-information and the pointer to the value are necessary when comparing interface pointers. However, I don't understand the utility in there being a distinction at a language or ABI level between an interface pointer to a null value and a null pointer to the interface <metadata, value> tuple. In what situations is the semantic distinction useful? There may be some domains where the semantic difference is important, but it seems to me that in those cases you're better off using a field of an enumerated type, for the sake of cleaning up the semantics of nil interfaces.
Though, coming from languages with algebraic data types, (and C++, where they had the good sense to make null references undefined behavior), cleaning up semantics of Nil in a language with Hoare's Billion Dollar Mistake is a bit like passing a public safety ordinance requiring fire extinguishers to be on hand at all gasoline fights... definitely helpful on the one hand, but seeming to miss an understanding of the root problem on the other.
Interfaces aren't pointers, they are a value type (it's a tuple as you describe).
The distinction for typed nil is useful because some receivers may work with a nil value. This whole thread enumerates the disadvantages pretty clearly, but I just wanted to point out that it _can_ be useful.
I do think compiler warnings would be helpful when this kind of thing happens implicitly, to prevent new language users from being confused for extended periods.