If we use null we have a nullable type ?T which is different to a null reference. Our contract clearly defines that the find routine either results in null ( ? ) or a value of the constrained type T . Think back, in the null reference case our type would be T , and not ?T , but it would still be possible that the caller receives null, which is after all a violation of our contract.

This all sounds nice but I still have to check for null everywhere, like in the null reference case!

Actually, you only have to check if a contract clearly defines that something can be null, and not everywhere. Note further that the same is true for exceptions. The only difference is that unhandled exceptions are easier to fix later because a single symbol at the outermost point of the program can take care of all the sloppy parts of the code that did not properly handle their exceptions.

Fixing all parts to account for a nullable type, on the other hand, is much more involving, and this is probably the reason why some advocate exceptions over nullable types. Then again, are we not all sloppy, and tend to forget things? We are human, are we not? Nobody produces perfect code at all times — despite some claiming to do so — or is capable of thinking of everything, always, everywhere.

At this point, those parts of the audience that got in touch with functional programming to some extend will shout “maybe monad”. And indeed I already mentioned the option types multiple times without going into any detail. An option type or maybe monad is like a checked exception in Java. This might still be unclear to the average PHP developer, let me explain the latter first to get back to the former afterwards.

Checked exceptions are exceptions which are part of a routine’s signature, consider the following hypothetical PHP code:

function f() throws Exception {}

Any code calling that routine is required to either enclose it in a try-catch block for that particular exception — or any of its superclasses — or extend its own signature to throw it — or any of its superclasses — as well. These constraints are enforced by the compiler, like type constraints in an inheritance chain.

An option type is similar in the sense that it cannot be ignored, like checked exceptions, and is part of a routine’s signature. However, it does not unroll the stack, and is not able to act like a goto. An option type instance encapsulates a value, and it allows access to that value only through its routines. Hence, anyone who wants to get a hold of that encapsulated value must go through those routines.

In other words, it is a safety net preventing us silly humans from forgetting that we are dealing with a possibly absent value. This sounds like the perfect solution to the dilemma we are facing here, plus, we can enrich the option type with other useful functionality. This is nice but it obviously adds overhead — there is no such thing as a free lunch.

The option type must be created, memory must be allocated, and we have to verify via method calls whether we received a meaningful value or not. In practice this overhead is negligible, but PHP comes with other obstacles that prevent us from having nice options: no support for generics.

In order to not lose type information we have to create a specific option class — a special case type implementation— for every possible type in the complete program. While this results in the best usability, it also results in the worst maintainability, and increases the overhead of these options tremendously. This is a problem that pops up all the time in PHP due to the simple type system, and aforementioned missing support for generics.

To null or not to null, that is the question!

This boils down to the question whether one is comfortable with using exceptions for control flow or not. Nullable types and option types are almost always the better choice. However, they are a heavy burden in PHP that should not be underestimated. This is due to the fact that right now it is possible to declare nullable types only, but not to work with them directly in a safe manner. They always require a guarding if condition that unnecessarily pollutes the code.

Attempts to improve the ergonomics of nullable types were made long before nullable types even landed in PHP — with the Nullsafe Calls RFC. Nullsafe calls combined with an intelligent type checker result in a catch all null object implementation without the overhead of option types, or actual null object implementations. On top of that they do not require support for generics to properly retain type information. In fact, there are languages that already perfected this:

This is the same example we had initially but written in Ceylon where we have to explicitly allow x to be null. The program does not compile if we are not declaring x to be nullable but call it with null, it also does not compile if we call plus directly on x without the nullsafe call operator ?. that precedes the method call. Of course, the overhead of performing such deep introspections on the source is not feasible in an interpreted language like PHP. However, static analysis tools are capable of adding that already.

Conclusio

Null references are a nasty problem. However, they are not a real world problem in PHP software since actual references are seldom used. Experienced developers do not use them because they know the side effects and beginners avoid them because they do not really know what they are doing after all.

Nullable types, or the type and value null, are very useful in programming languages and required to indicate the absence of a meaningful value. It does not matter how they are called (null, nil, ?, nothing*, …) or if they are not directly available in userland, e.g. Rust or most functional languages which make use of the optional type instead of null.

*) Nothing is most commonly used as a bottom type and not as null, the PHP equivalent here would be void.