It's worth noting that having a NULL, somewhere, is not so much a problem as forcing types to have a NULL. In this respect, e.g. Python and Rust both get it right: while Python has a None, and Rust has std::ptr::null(), an object that is a string cannot be any of those, because those are different types (NoneType and raw pointer, respectively). C's problem is that a string could be null, and there's no syntax for a non-null string.<p>Python's problem, meanwhile, is that any parameter could be <i>any</i> type. In C, you can pass NULL to a function expecting a non-null string. In Python, you can also pass 42, [], or the sqlalchemy module. None isn't special here. :)<p>Also, to quibble a bit: Rust's std::ptr::null() is just a raw pointer, and isn't actually <i>usable</i> in safe Rust. Actual safe pointer types are guaranteed non-null and you'd use Option<&T> to specify that they're nullable. Raw pointers cannot be dereferenced in safe Rust. It is true that std::ptr::null() is in Rust's standard library, but Foreign.Ptr.nullPtr is in Haskell's, and the purpose is the same (a special raw-pointer type only used for FFI purposes), so Rust isn't any worse than Haskell here.
<i>Uglier than a Windows backslash, odder than ===, more common than PHP, more unfortunate than CORS, more disappointing than Java generics, more inconsistent than XMLHttpRequest, more confusing than a C preprocessor, flakier than MongoDB, and more regrettable than UTF-16, the worst mistake in computer science was introduced in 1965.</i><p>That could be the greatest intro sentence ever seen on Hacker News.
One of the problem's Maybe still has is in the deeper question of "why are you expecting None to be here?". Don't get me wrong, there are valid cases for this, and Maybe is certainly preferable to null across the board, but I think the movement to Maybe in the greater programming space will in many cases practically result in trading one set of explicit errors (crashes) for (a more subtle?) set of errors (behavioral). In particular, this style of programming will become frequent (from Swift documentation):<p><pre><code> if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
</code></pre>
Or from this blog post's own example:<p><pre><code> option.ifPresent(x -> System.out.println(x));
</code></pre>
In other words, I think the core problem still hasn't been attacked and we may end up in the same situation we were in with exceptions originally: programmers will just throw up their hands and wrap everything in a Maybe/Optional and/or just maybe-protect until the compiler stops bugging them. At the end of the day, if you ? all your values then you end up with something equivalent to having everything be nullable and correctly null-checking them.<p>Obj-C had a form of this with nil calling of methods silently doing nothing (so you end up with methods that "conveniently" don't happen, and don't crash! when the receiver is nil). However, despite having lots of legitimate uses (don't bother checking your delegate exists), it can still very silently sneak in to other parts of the code.
The mistake is an unsafe null: making every object type carry a value in its domain which says "Oops, though my type says I'm a Foobar, I'm not actually an object, la la la! Have a free exception on me, in your face!"<p>Lisp's NIL is brilliant. It's in its own type, the null type! And it's the only instance of that type. No other type has a null instance.<p>Null references in the language simply reflect the tension between the static type system which is supposed to assure us that if static checks pass, then values are not misused, and the need to have sometimes (at the very least) a "Maybe" type: maybe we have an object, or maybe not. Null references are the "poor hacker's Maybe": let's make every type support a null value, so that Maybe shows up everywhere.<p>Maybe, or something like the Lisp NIL, are themselves not mistakes by any stretch of the imagination. Sometimes you really need the code to express the idea that an object isn't there: directly, not with hacks like some representative instance, and some booelan flag which says "that's just a decoy indicating the object isn't there". You <i>want</i> the exception if the decoy is used as if it were an object; that's a bug. Something is trying to operate on an object in a situation when there is no object; safely operating on a decoy just sweeps this under the rug.
(Yet, not always: sometimes operating on the decoy is fine. Lisp's NIL lets us specialize a method parameter to the NULL class and deal with it that way in one place.)
We don't need to get rid of null, we need <i>more, type-specific and context-carrying</i> nulls: types need a way to signal various error states via an enumeration of instance constants that carry the error state in a type-compatible manner without some syntactically crappy mechanism like Maybe.<p><pre><code> class Connection {
//Error instances
BAD_ADDR("The given address was not correct...")
...
}
</code></pre>
These constants should throw, just like a null value would, but with the specific error-state message, and should be testable for both the general and specific cases.<p>Try/catch has its place, but the persistence of null-as-error-state behavior among programmers shows that signalling-via-return-value is a practical and intuitive way to handle error states. We should make that better, rather than simply tipping our nose at null.
I agree with the article, yet, despite what the table at the end suggest I got significantly less problems with NullPointerExceptions since I switched from Java to C++ for my work.<p>C++ has an inbuilt alternative: references. References in conjunction with the Null Object Pattern have solved my problems until now.<p>References simply cannot be null -- and I generally do not use raw pointers in my code unless some library forces me to.
The title originally matched the blog post ("The worst mistake of computer science"), but then it was changed by a moderator.<p>Maybe it could have been a less drastic change?<p>"NULL, the worst mistake of computer science"<p>or<p>"The worst mistake of computer science: NULL"
Here is what is wrong with Option.<p>option.ifPresent(x -> System.out.println(x));<p>So instead of just checking to see if it is NULL you want me to create an instance of a specialized class that holds my variable that has a method that acts like an "if" statement that I need to pass an anonymous function to that receives the value I already have?<p>Why not just do:<p>x && System.out.println(x)<p>or:<p>System.out.println(x) if x<p>Or if you want to skip over the rest of the logic if it is null:<p>return unless x<p>System.out.println(x)<p>I don't see why I need to introduce a new type system and complicate things.<p>For default values you want me to create an instance of specialized class that holds my variable and has a method that allows me to get my value or return a default value?<p>option.orElseGet(5)<p>So instead of a memory lookup I now need the overhead of an entire function call?<p>Why not just do:<p>x ||= 5<p>Or better yet, just put it in your function declaration as a default value:<p>myFunc = (x=5, y, z) -> ...<p>The one benefit I see is type safety but it is extremely rare for experienced programmers working in dynamic languages to have bugs related to type.<p>You are layering on abstractions and forcing programmers to go through hoops just to get at their data. It is more complicated and increasing the cognitive load.<p>There's also the fact that you are going through functions and classes instead of just memory accesses. This makes code less performant as well.
C++ introduced non-null references. But they didn't enforce them. There are still "I'm so l33t I can use null references" people. The new move semantics use null references for things moved from. C++ is trying to do Rust-like borrow checking without a borrow checker. Errors result in de-referencing null and crashing.<p>Rust doesn't have null, but it has Option<T>, which is often syntactic sugar for null.<p>It's not that null is bad in itself. It's that having variables which might be null need to be distinguished from ones which can't be null.
>NULL is a terrible design flaw, one that continues to cause constant, immeasurable pain<p>Exaggeration much? Certainly not "the worst mistake of computer science". IPv4 is much worse, just for one example. NULL isn't even visible to end-users, many mistakes in CS are quite visible and really impact non-programmers' lives. NULL is just the color of the wallpaper in the engine room.
No mention of SQL, where NULL's behavior in the standard can lead to some quirky behavior that I've seen bite back in poorly designed systems. I've included a brief illustrative example. The expectation is that the UNION of two WHERE clauses, one using IN and the other using NOT IN should be equivalent to the same SELECT without any WHERE:<p><pre><code> WITH NullCTE AS
(SELECT a.*
FROM
(VALUES (NULL), (1), (2)) a (Number)
)
,One AS
(SELECT Number = 1)
SELECT *
FROM NullCTE nc
WHERE nc.Number NOT IN
(SELECT Number
FROM One)
UNION
SELECT *
FROM NullCTE nc
WHERE nc.Number IN
(SELECT Number
FROM One)
</code></pre>
Running this will give you back a two-row table, containing 1 and 2, but the NULL is excluded from both WHERE conditions.<p>The naive expectation is that the combination of a condition and the NOT of that condition cover all possible circumstances.
I was with the article until the part about null terminators on strings. Null terminators are nothing like a NULL reference. We could just as easily have dollar-sign-terminated strings and no one would be conflating that idea with the concept of NULL.<p>Also, NULL pointers are the least problematic type of pointer if you ask me. If a pointer is NULL it is not likely a security problem, and certainly not on its own. Accidentally using a NULL pointer will cause a crash, but accidentally using <i>any other</i> pointer could cause unlimited damage.
Many modern languages (Kotlin, Rust, Swift) handle this problem well.<p>Though I'm not sure if that problem is really that huge. Bad code will break in many ways. And breaking on nulls usually isn't that dangerous, data isn't corrupted and stack is safe.<p>There's another mistake of computer science in my opinion is inefficient array bounds checking and implicit integer overflow behavior. And those mistakes are more dangerous, they lead to data corruption and exploits.
Well the article doesn't talk about performance, but at least for the double-maybe in the store example, there is one level of extra indirection in the machine code: return pointer to maybe, then return pointer from string from it.<p>If you care about performance, you really need two predicate values to cover this case. You could have (void * )0 for no entry, and (void * )1 for entry exists, but is blank.
I've never had a problem with NULL as a "value". Null is the absence of content, a container that is truly empty, and that is frequently useful. As an example it allows you to differentiate between a numerical field that was never populated vs. one specifically set to Zero. This is often an important distinction.
> If x is known to be a String, the type check succeeds; if x is known to be a Socket, the type check fails.<p>So the author is trying to explain what the null pointer exception is, but uses the concept of sockets in his example. I wonder, how many people know what a socket is but don't know what null pointer exception is?
NULL is okay as long as you pretend it doesn't exist. I mean, in these languages, uninitialized variables exist at some point (fields start uninitialized in constructor bodies, etc), and that's why there's null, instead of defining them with some garbage value that has undefined behavior. But the right solution for users is to just <i>pretend</i> that it can't exist, and that uninitialized variables have a garbage value.<p>Of course, that's idealistic, most languages don't have some Option<T> type without a performance penalty, and pre-existing libraries exist. (Usually you should design around needing Option<T> too.)<p>The right language design decision for these languages (managed languages like Java) might have been to make uninitialized references have a garbage value that reliably throws an exception when used (i.e. null) -- you can copy the reference and pass it around, but you <i>can't</i> compare it for equality and any attempt to inspect its value results in an exception.
Another reason that, even despite sharing a lot of syntax with PHP, Hack is one of my favorite languages:<p><a href="http://docs.hhvm.com/manual/en/hack.nullable.php" rel="nofollow">http://docs.hhvm.com/manual/en/hack.nullable.php</a>
For statically typed languages this is definitely an issue, but for dynamic languages less so. In Python, I wouldn't use an optional value,<p><pre><code> x is None</code></pre>
seems to be just fine.
I'm still waiting for<p><pre><code> std::optional</code></pre>
for C++.
lol the null / undefined issue in javascript is further exacerbated by the fact there is no int type and everything is just a "number".... yet the number 0 is still treated the same as null/undefined by js special forms like "if" and "==". This is particularly hilarious because it leads to some null-check bugs like:<p>if ( user.getScrollPosition() ) { whatever(); } else { die(); }<p>99% of the time, the code would be fine, but if the user scrolls just right, the whole thing would die. Stuff like this is literally death to debug because this bug is effectively indeterministic and can't be reproduced.
> 4. NULL makes poor APIs<p>Then don't use it. The example given is a bad design. No phone number and not in cache should not return the same value. That bad design has nothing to do with nil, Return '' for no phonenumber.
I'm still not entirely convinced that NULL is a problem. But how NULL (and pointer types in general) work in C leads to much, much greater problems.<p>Though, I think uninitialized variables or memory might be just as bad.
NUL-terminated strings aren't <i>that</i> bad:<p>* unlike Pascal-style strings, they can be usefully sliced, especially if you can modify them strtok-style.<p>* unlike (ptr,len) "Modern C buffers"/Rust-style strings, references to them are pointer-sized, and they can be used as a serialization format.<p>This makes the kind of application that is based on cutting pieces of a string and passing them around a good measure faster, especially compared to say C++'s "atomically reference-counted, re-allocating at the slightest touch" std::string.<p>This style of programming is not particularly popular nowadays, so buffer-strings are better-fitting. Its main problem is its multitude of edge-cases, which tend to demonstrate C's "every bug is exploitable" problem well.
I'm not a c++ expert, but doesn't<p><pre><code> char *myChar = 0;
std::cout << *myChar << std::endl; // runtime error
</code></pre>
compile because it's undefined behavior?
In case the author is reading this thread:<p>The entry for C++ in the final tables is:<p><pre><code> C++ | NULL | boost::optional, from Boost.Optional
</code></pre>
It does ignore that C++ has nullptr since C++11
> if (str == null || str == "")<p>Why not have null (and emptystring) eval to false? And<p><pre><code> if not str:
str = 'wordup'</code></pre>