Calling methods on null objects in Go and PHP

Submitted by Frederic Marand on

Call a method on a nil pointer (null object) ? This is a classical bug. What if it somehow turned out to be a valid and useful construct in some languages, say in Go ? And what about PHP ?

Observing classic "bug" behaviour, in PHP

Let us try this in PHP, and see the exact behavior:

PHP behaves as expected in traditional C family languages: the method call works normally on the created instance $p1, but fails with a PHP fatal error when invoked on a null object.

Go working behavior, and why this is useful

As the late Alan Perlis once famously said: 19 - A language that doesn't affect the way you think about programming, is not worth knowing. . Go's take on this specific question looks like one of these cases where a language changes your assumptions about a specific aspect of programming.

The fragment we just saw in PHP has a fairly similar structure in Go, but note the difference in method syntax, in which the object is an explicit parameter.

In Go, as this example demonstrates, calling a method on a such nil object is possible and opens all kinds of interesting possibilities, typically by internalizing the nil checks in the methods instead of having to perform them in the calling code, making objects simpler to use.

Why can this work in Go ?

Thinking on the typical ways in which C-derived languages call methods, or actually C itself when using method pointers, this behavior can seem like magic: non-static methods are typically located by following a pointer from the object instance to its class, and from there to its (virtual) method table. Which means that from the typical nil == 0x00000000 representation, there is no information to follow towards a class, hence no way to locate a methods table.

However, in Go, nil != nil, as detailed in the Go Faq, under Why is my nil error value not equal to nil ?

See the previous link for details in the case of nil and interfaces, but the dump in the previous snippet contains a hint : notice that is displays the nil value for p2 as: (*main.Person)(<nil>), not just as a nil : since go has static typing, this "nil" value actually contains type information, along with the actual 0x00000000 memory address, which is enough to locate the method to invoke, and pass it - you guessed - the 0x00000000 pointer, which is exactly a typed nil.

UPDATE 2014-09-21: Chris Siebenmann elaborates on this finding by relating it with the lack of inheritance in Go in his One reason why Go can have methods on nil pointers article.

Is there any workaround in PHP ?

If you are programming in PHP, you are likely already considering how you could mimic the behavior in PHP. Let us check if PHP can provide the information on a null:

<?phpecho gettype(NULL);/* NULL */?>

So, no, PHP does not provide type information at the user programming level to go from a NULL to the methods on an object. Hence the simple solution : provide the information from userland code, by calling the method with a static call instead of an instance call, as in the next snippet :

Quick win : we now avoid errors, and can return meaningful information on null values too.

However, this remaing just a quick hack, not so useful in practice because it means that we now have hardcoded the caller class, and the code containing this is now a bit more intractable, violating the Open/Closed and Liskov principles : we are no longer invoking a method according to an interface (abstraction), implemented by the class of the object instance, but to a concretion (class), meaning implementations in inherited classes would still call the base class.

Since PHP 5.3, it has been possible to go further, using the caller information to know the called class, and invoke a method on it, as long as the method takes parameters by class, but not by interface. In that case the Open/Closed principle can be respected, since the inherited implementations can transparently use the called class in the original, but the Liskov violation remains, since the method can not receive parameters by interface hinting.