A = nil, A = B, B != nil with pointers and interface


(Andrea Spacca) #1

What version of Go are you using (go version)?

the one on go playground

Does this issue reproduce with the latest release?

if the go playground has the latest release yes

What operating system and processor architecture are you using (go env)?

not applicable

What did you do?

https://play.golang.org/p/jM2mOSkKo-a

What did you expect to see?

either be printed:

interfaceSlice[1] == ps[1]
ps[1] == nil
interfaceSlice[1] == nil

or only:

ps[1] == nil

What did you see instead?

printed:

ps[1] == nil
interfaceSlice[1] == ps[1]

I’d like to have some explanation about the reason for this


(Jon Calhoun) #2

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.


(Andrea Spacca) #3

thanks jon, the only detail missing from your answer I think is that apparently the equal operator in case the left operand doesn’t have an explicit type (like 12 or nil) assumes it to be the same of the right operand