Many languages have some way to denote a null-pointer which essentially points to nowhere. Some kind od an initial dummy value. In C it is
NULL, in Python it is
None which is its own type (
NoneType, a type only
None belongs to).
Go language has it as well. It is
nil, and you’re probably familiar with it already. But one thing you might not think of is that it sometimes has a type, and sometimes doesn’t. Confusing already? Let’s explore further.
Let’s take a look at a simple program.
Will it compile? No, it won’t. The compiler will print
use of untyped nil error. Here we are trying to assign a
nil to a variable without providing its type; we expect the compiler to infer it. It can’t. What is it? A pointer to an integer? A string? An array? A structure? No way to tell in advance. And Go requires knowledge of types to compile a program; it’s not C language that lets you declare pretty much anything in a block of memory and then treat it later as something else (something that I love and hate about the C language).
Tell me more!
The following program, on the other hand, is perfectly valid.
The compiler knows the type now, and it is a pointer. Assigning a null value to it is fine.
If you create another variable, initialize it to
nil and then try to compare the two
it will print
true. Sounds reasonable.
Pointers? Not only addresses.
Now try something like this
invalid operation: a == b (mismatched types *int64 and *string). But they are both nil, you might say. Doesn’t matter! Unlike C, where pointers simply hold addresses, in Go they have types, and variables of different types cannot be compared. Therefore we’re looking at a slightly different
Most of the time it won’t bother you; why compare different types anyway? Yet there is one example, that I’m going to show you now, that made me think about
nil having a type in the first place.
Nils with… methods?
So here’s a simplified interpretation of the code that gave me something to think about.
Let’s abstract ourselves away from what’s going on in the
main() for a moment. We declared a simple structure
A, then declared a structure
B containing a pointer to an instance of the structure
A, and finally declared a structure
C containing a pointer to an instance of the structure
B. And we created getter methods for
C that return the pointers they contain. If the structure itself is
nil, it will return
nil (it could do whatever we wanted, to be honest, like logging or returning some default pointer, etc.).
Wait! The structure itself is
nil? How can a
nil have a method? Well, in Go, it can!
main(), we have declared a
C structure that contains a
nil pointer. The following statement should not cause any confusion. It just returns
nil, right? Yes, it’s all going smoothly.
Now the next statement is interesting.
GetB() returns a
nil here, right? If we remember how we make a program panic every time we forget to check whether an
nil or not and accidentally call
Error() method on a
nil (guilty as charged), we might expect that
GetA() called on a
nil would make it panic.
And yet, it does not! The
nil returned by
GetB() is not some generic null pointer value, it is actually typed. It has a type
*B, and that type has a method
GetA(). And that method’s implementation checks whether the pointer to
B itself is
nil or not.
When I first saw this code, that was my biggest confusion. I expected
GetA() to panic in this case. And it would, but for the fact that
nil in Go has much more to it than pointing into the void of nothingness.
There are several StackOverflow questions like this and this that helped me get to the bottom of all this. Maybe not even the bottom yet, since Go, just like pretty much every programming language, has so many tidbits many of us might not be aware of. Or we might use them every day without even noticing that we would instantly
panic, had we given it some thorough thought. Pun intended.
Thanks for tuning in!