This is kinda hard/weird to explain, but I don’t believe it is a bug. It is just a weird caveat in the language that is tricky at first and may take some time to get used to.
I’m going to take a stab at explaining, but if it isn’t enough let me know.
First, we need to understand that every pointer in Go is going to basically have two pieces of information: (type, value)
This is why we can’t have a nil value without a type. The following code WILL NOT compile.
n := nil // doesn't work because we don't know the type!
Instead we have to use a typed pointer and assign it to nil:
var a *int = nil
var b interface{} = nil
Now if we print out the type of each of these we get something different back.
var b interface{} = nil
fmt.Printf("%T\n", a) // prints *int
fmt.Printf("%T\n", b) // prints <nil>
Note: %T
is used to print out a value’s type with fmt.Printf
So it looks like when we assign nil
, the hard-coded value, to a *int
variable it will get the type *int
. If we assign nil
, the hard-coded value, to a interface{}
variable it will get the type <nil>
(this seems weird I know).
Okay, so we know all pointers have a (type, value)
associated with them and we have seen what happens when we assign each one to a variable. Let’s jump back to an example snippet similar to what you posted.
func main() {
var a *int = nil
var b interface{} = a
fmt.Println("a==nil:", a == nil)
fmt.Println("b==nil:", b == nil)
fmt.Println("a==b:", a == b)
}
https://play.golang.org/p/Bfh0S3UTqB1
In this case we assign a
to the hard-coded nil
, then assign b
to a
, then check for equality. This should render the same results you saw in your report.
a==nil: true
b==nil: false
a==b: true
Now the question is, why isn’t b == nil
evaluating to true?
The short answer - because we assigned b
to a
and NOT to nil
, b
now retains both a
's value and a
's type. We can see this if we were to print out the type and value of both a
and b
.
fmt.Printf("a=(%T,%v)\n", a, a)
fmt.Printf("b=(%T,%v)\n", b, b)
https://play.golang.org/p/SFyTaVHaVz6
We get the output:
a=(*int,<nil>)
b=(*int,<nil>)
What is interesting is that if we were to assign b
directly to nil
this is NOT the same results we would get:
b = nil
fmt.Printf("b=(%T,%v)\n", b, b)
https://play.golang.org/p/eCxQH9LY2Gd
Output:
b=(<nil>,<nil>)
This is important, because when we compare b
with a hard-coded nil
that means the hard-coded nil is going to be given a different type than our b
variable has. That is, when we write:
var a *int = nil
var b interface{} = a
fmt.Println("a==b:", a == b)
What we are really comparing is somethign like (*int, nil)
with (<nil>, nil)
and as you can see those two have different types.
Now, back to the *int
variable a
- why does a == nil
work? Well when we want to assign nil to a *int
variable, the only valid type is *int
so the compiler is forced to give that nil value the same type. That is, when we write:
var a *int = nil
fmt.Println("a==nil:", a == nil)
We are comparing (*int, nil)
with another (*int, nil)
because the hard-coded nil has to be coerced into the correct type for the comparison to work.
You can actually see this with other types as well, such as numbers and interfaces. Let’s take a look:
var a int = 12
var b float64 = 12
var c interface{} = a
fmt.Println("a==12:", a == 12)
fmt.Println("b==12:", b == 12)
fmt.Println("c==12:", c == 12)
fmt.Println("a==c:", a == c)
fmt.Println("b==c:", b == c)
fmt.Printf("a=(%T,%v)\n", a, a)
fmt.Printf("b=(%T,%v)\n", b, b)
fmt.Printf("c=(%T,%v)\n", c, c)
https://play.golang.org/p/yNV0c6hAbtX
Output:
a==12: true
b==12: true
c==12: true
a==c: true
b==c: false
a=(int,12)
b=(float64,12)
c=(int,12)
So a == 12
, b == 12
and c == 12
, but if we compare b == c
we get back false. What?!?!
Again, it goes back to the underlying types:
a=(int,12)
b=(float64,12)
c=(int,12)
a
and c
have the int
type. b
has a float64
type, so when we compare them with hard coded values like 12
those need coerced into a type before the comparison can happen.
Another interesting note specific to numbers is that when comparing 12
to an interface, the compiler will always coerce it into an int
. This is similar to how nil
gets coerced into (<nil>, nil)
when compared to an interface, and we can demonstrate this by changing our last code snippet to instead be:
var b float64 = 12
var c interface{} = b
fmt.Println("c==12:", c == 12)
fmt.Printf("c=(%T,%v)\n", c, c)
fmt.Printf("hard-coded=(%T,%v)\n", 12, 12)
https://play.golang.org/p/9gEsrSO9zTS
Output:
c==12: false
c=(float64,12)
hard-coded=(int,12)
Now c == 12
returns false because (float64, 12)
is not the same as the hard-coded (int, 12)
because it has a different type.
So TL;DR - when we compare hard-coded values with variables the compiler has to assume they have some specific type and follows some set of rules to make this happen. Sometimes this can be confusing, but over time you get used to it and have a better idea when you should probably do something like this:
var a *int = nil
var b interface{}
if a == nil {
b = nil
}
Instead of directly assigning b
to a
.
Disclaimer: I haven’t studied the compiler or the inner-workings of Go in any real capacity, so if any of this is inaccurate someone please let me know and I’ll fix it. This is all based on observation and other articles I have read about Go.