2014-09-21
One reason why Go can have methods on nil pointers
I was recently reading an article on why Go can call methods on
nil
pointers
(via)
and wound up feeling that it was incomplete. It's hard to talk about
'the' singular reason that Go can do this, because a lot of design
decisions went into the mix, but I think that one underappreciated
reason this happens is because Go doesn't have inheritance.
In a typical language with inheritance, you can both override methods on child classes and pass a pointer to a child class instance to a function that expects a pointer to the parent class instance (and the function can then call methods on that pointer). This combination implies that the actual machine code in the function cannot simply make a static call to the appropriate parent class method function; instead it must somehow go through some sort of dynamic dispatch process so that it calls the child's method function instead of the parent's when passed what is actually a pointer to a child instance.
In non-nil
pointers to objects, you have a natural place to put
such a vtable
(or rather a pointer to it) because the object has actual storage
associated with it. But a nil
pointer has no storage associated
with it and so you can't naturally do this. That means given a nil
pointer, how do you find the correct vtable? After all it might be
a nil
pointer of the child class that should call child class
methods.
Because Go has no inheritance this problem does not come up. If
your function takes a pointer to a concrete type and you call
t.Method()
, the compiler statically knows which exact function
you're calling; it doesn't need to do any sort of dynamic lookup.
Thus it can easily make this call even when given a nil
.
In effect the compiler gets to rewrite a call to t.Method()
to something like ttype_Method(t)
.
But wait, you may say. What about interfaces? These have exactly the dynamic dispatch problem I was just talking about. The answer is that Go actually represents interface values that are pointers as two pointers; one which is the actual value and another points to (among other things) a vtable for the interface (which is populated based on the concrete type). Because Go statically knows that it is dealing with an interface instead of a concrete type, the compiler builds code that calls indirectly through this vtable.
(As I found out, this can lead to a situation where
what you think is a nil
pointer is not actually a nil
pointer
as Go sees it because it has been wrapped up as an interface value.)
Of course you could do this two-pointer trick with concrete types
too if you wanted to, but it would have the unfortunate effect of
adding an extra word to the size of all pointers. Most languages
are not interested in paying that cost just to enable nil
pointers
to have methods.
(Go doesn't have inheritance for other reasons; it's probably just a
happy coincidence that it enables nil
pointers to have methods.)
PS: it follows that if you want to add inheritance to Go for some
reason, you need to figure out how to solve this nil
pointer with
methods problem (likely in a way that doesn't double the size of
all pointers). Call this an illustration of how language features
can be surprisingly intertwined with each other.