Error Compiling Code Please Try Again Later Foo bar
Uglier than a Windows backslash, odder than ===
, more common than PHP, more than unfortunate than CORS, more disappointing than Java generics, more than inconsistent than XMLHttpRequest, more confusing than a C preprocessor, flakier than MongoDB, and more regrettable than UTF-16, the worst error in calculator science was introduced in 1965.
I call information technology my billion-dollar mistake…At that time, I was designing the first comprehensive blazon system for references in an object-oriented language. My goal was to ensure that all use of references should exist absolutely safe, with checking performed automatically by the compiler. Simply I couldn't resist the temptation to put in a null reference, simply because it was and so piece of cake to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
– Tony Hoare, inventor of ALGOL West.
In commemoration of the 50th anniversary of Sir Hoare's null, this article explains what null is, why it is so terrible, and how to avert it.
What is incorrect with NULL?
The short answer: NULL is a value that is not a value. And that's a problem.
It has festered in the most pop languages of all fourth dimension and is now known by many names: NULL, zip, null, None, Zip, Nil, nullptr. Each linguistic communication has its own nuances.
Some of the problems caused past NULL apply simply to a item language, while others are universal; a few are merely different facets of a unmarried result.
NULL…
- subverts types
- is sloppy
- is a special instance
- makes poor APIs
- exacerbates poor language decisions
- is difficult to debug
- is non-composable
1. NULL subverts types
Statically typed languages cheque the uses of types in the program without actually executing, providing certain guarantees about program behavior.
For example, in Coffee, if I write ten.toUppercase()
, the compiler will audit the type of x. If x
is known to exist a String
, the type check succeeds; if x
is known to be a Socket
, the type check fails.
Static blazon checking is a powerful help in writing large, circuitous software. But for Java, these wonderful compile-fourth dimension checks suffer from a fatal flaw: whatever reference can exist null, and calling a method on zippo produces a NullPointerException
. Thus,
-
toUppercase()
can exist safely called on anyString
…unless theString
is cypher. -
read()
tin can be called on anyInputStream
…unless theInputStream
is cypher. -
toString()
tin be called on whatsoeverObject
…unless theObject
is nix.
Coffee is not the only culprit; many other blazon systems have the same flaw, including of course, AGOL W.
In these languges, Naught is above type checks. Information technology slips through them silently, waiting for runtime, to finally burst free in a shower of errors. NULL is the aught that is simultaneously everything.
two. Zippo is sloppy
There are many times when it doesn't brand sense to accept a null. Unfortunately, if the language permits anything to be null, well, annihilation can be null.
Java programmers take chances carpal tunnel from writing
if (str == null || str.equals("")) { }
It's such a common idiom that C# adds String.IsNullOrEmpty
if (string.IsNullOrEmpty(str)) { }
Abhorrent.
Every time you write lawmaking that conflates null strings and empty strings, the Guava squad weeps.
– Google Guava
Well said. Merely when your type system (e.1000. Coffee, or C#) allows Naught everywhere, you cannot reliably exclude the possibility of NULL, and information technology's near inevitable it will current of air up conflated somewhere.
The ubiquitous possibility of null posed such a trouble that Java 8 added the @NonNull
annotation to try to retroactively gear up this flaw in its blazon system.
3. Null is a special-instance
Given that NULL functions as a value that is non a value, NULL naturally becomes the subject of various forms of special treatment.
Pointers
For case, consider this C++:
char c = 'A'; char *myChar = &c; std::cout << *myChar << std::endl;
myChar is a char *
, meaning that it is a pointer—i.e. the memory address—to a char
. The compiler verifies this. Therefore, the following is invalid:
char *myChar = 123; // compile error std::cout << *myChar << std::endl;
Since 123 is not guaranteed to be the address of a char
, compilation fails. However, if we modify the number to 0 (which is NULL in C++), the compiler passes it:
char *myChar = 0; std::cout << *myChar << std::endl; // runtime mistake
Every bit with 123, NULL is not actually the address of a char
. Yet this fourth dimension the compiler permits it, because 0 (NULL) is a special case.
Strings
Yet another special example happens with C's goose egg-terminated strings. This is a bit different than the other examples, equally in that location are no pointers or references. Simply the idea of a value that is not a value is still present, in the form of a char that is not a char.
A C-string is a sequence of bytes, whose end is marked by the NUL (0) byte.
76 117 99 105 100 32 83 111 102 116 119 97 114 101 0 Fifty u c i d S o f t due west a r eastward NUL
Thus, each character of a C-string can be any of the possible 256 bytes, except 0 (the NUL character). Not only does this brand string length a linear-fourth dimension functioning; fifty-fifty worse, information technology means that C-strings cannot be used for ASCII or extended ASCII. Instead, they tin only exist used for the unusual ASCIIZ.
This exception for a atypical NUL character has caused innumerable errors: API weirdness, security vulnerabilities, and buffer overflows.
NULL is the worst CS error; more specifically, NUL-terminated strings are the nigh expensive one-byte mistakes.
iv. Nothing makes poor APIs
For the next example, we volition journey to the land of dynamically-typed languages, where NULL volition over again evidence to exist a terrible mistake.
Key-value store
Suppose we create a Ruby-red form that acts as a key-value shop. This may exist a cache, an interface for a cardinal-value database, etc. We'll make the general-purpose API unproblematic:
form Store ## # acquaintance cardinal with value # def prepare(cardinal, value) ... stop ## # go value associated with key, or return nil if in that location is no such primal # def get(key) ... stop end
We can imagine an analog in many languages (Python, JavaScript, Coffee, C#, etc.).
At present suppose our program has a slow or resource-intensive way of finding out someone's phone number—peradventure by contacting a web service.
To improve performance, we'll use a local Shop
every bit a cache, mapping a person'due south name to his phone number.
store = Shop.new() store.set('Bob', '801-555-5555') shop.get('Bob') # returns '801-555-5555', which is Bob's number store.become('Alice') # returns nil, since it does non accept Alice
However, some people won't take phone numbers (i.e. their phone number is nil). We'll still enshroud that data, and then we don't accept to repopulate it afterwards.
store = Shop.new() shop.set up('Ted', nil) # Ted has no phone number shop.become('Ted') # returns nil, since Ted does non accept a phone number
But now the meaning of our result is cryptic! Information technology could mean:
- the person does not exist in the cache (Alice)
- the person exists in the cache and does not take a phone number (Tom)
One circumstance requires an expensive recomputation, the other an instantaneous answer. Merely our code is comparatively sophisticated to distinguish betwixt these two.
In real lawmaking, situations like this come up up frequently, in complex and subtle means. Thus, simple, generic APIs can of a sudden become special-cased, confusing sources of sloppy nullish behavior.
Patching the Shop class with a contains()
method might help. But this introduces redundant lookups, causing reduced functioning, and race conditions.
Double trouble
JavaScript has this same issue, but with every single object.
If a property of an object doesn't exist, JS returns a value to indicate the absence. The designers of JavaScript could accept chosen this value to be nothing
.
Merely instead they worried about cases where the property exists and is set to the value null.
In a stroke of ungenius, JavaScript added undefined
to distinguish a null property from a not-existent 1.
But what if the property exists, and is set to the value undefined
? Oddly, JavaScript stops here, and there is no uberundefined
.
Thus JavaScript wound up with non simply 1, merely 2 forms of NULL.
five. Goose egg exacerbates poor language decisions
Java silently converts between reference and primitive types. Add together in null, and things go fifty-fifty weirder.
For example, this does not compile:
int x = null; // compile error
This does compile:
Integer i = null; int x = i; // runtime error
though it throws a NullPointerException when run.
It's bad enough that member methods tin be called on nix; it'south fifty-fifty worse when yous never even see the method being called.
6. NULL is difficult to debug
C++ is a smashing example of how troublesome NULL tin be. Calling member functions on a Aught arrow won't necessarily crash the program. It's much worse: it might crash the programme.
#include <iostream> struct Foo { int 10; void bar() { std::cout << "La la la" << std::endl; } void baz() { std::cout << 10 << std::endl; } }; int main() { Foo *foo = NULL; foo->bar(); // okay foo->baz(); // crash }
When I compile this with gcc, the first call succeeds; the 2nd telephone call fails.
Why? foo->bar()
is known at compile-time, and so the compiler avoids a runtime vtable lookup, and transforms information technology to a static call like Foo_bar(foo)
, with this
equally the first statement. Since bar
doesn't dereference that NULL pointer, it succeeds. But baz
does, which causes a segmentation error.
But suppose instead nosotros had made bar
virtual. This means that its implementation may be overridden by a bracket.
... virtual void bar() { ...
As a virtual function, foo->bar()
does a vtable lookup for the runtime type of foo
, in case bar() has been overridden. Since foo
is Nil, the program at present crashes at foo->bar()
instead, all because nosotros made a function virtual.
int chief() { Foo *foo = Nil; foo->bar(); // crash foo->baz(); }
NULL has made debugging this code extraordinarily difficult and unintuitive for the developer of main
.
Granted, dereferencing Aught is undefined by the C++ standard, so technically nosotros shouldn't be surprised by whatever happened. All the same, this is a non-pathological, common, very uncomplicated, existent-world example of 1 of the many ways Nil can be capricious in practice.
7. NULL is non-composable
Programming languages are built around composability: the ability to apply one abstraction to another abstraction. This is perhaps the single near important feature of any language, library, framework, paradigm, API, or pattern pattern: the ability to be used orthogonally with other features.
In fact, composibility is really the cardinal issue behind many of these problems. For example, the Shop
API returning zilch
for non-existant values was non composable with storing zip
for non-existant phone numbers.
C# addresses some issues of Nothing with Nullable<T>
. Yous can include the optionality (nullability) in the type.
int a = 1; // integer int? b = 2; // optional integer that exists int? c = null; // optional integer that does not be
But it suffers from a disquisitional flaw that Nullable<T>
cannot apply to any T. It can only use to non-nullable T. For example, information technology doesn't make the Shop
problem any better.
-
cord
is nullable to begin with; you cannot make a not-nullablestring
- Fifty-fifty if
string
were non-nullable, thus makingstring?
possible, you all the same wouldn't be able to disambiguate the situation. There isn't acord??
The solution
NULL has become so pervasive that many just assume that it'southward necessary. We've had it for so long in and so many low- and loftier-level languages, information technology seems essential, similar integer arithmetics or I/O.
Not so! You can take an entire programming language without NULL. The problem with NULL is that information technology is a non-value value, a sentinel, a special example that was lumped in with everything else.
Instead, nosotros need an entity that contains information nearly (one) whether it contains a value and (ii) the contained value, if it exists. And it should exist able to "comprise" any type. This is the idea of Haskell'south Maybe, Java's Optional, Swift's Optional, etc.
For example, in Scala, Some[T]
holds a value of type T. None
holds no value. These are the two subtypes of Pick[T]
, which may or may not hold a value.
The reader unfamiliar with Maybes/Options may recall nosotros have substituted ane form of absence (Nada) for another form of absenteeism (None). Simply there is a departure — subtle, merely crucially important.
In a statically typed language, you cannot bypass the type arrangement past substituting a None for any value. A None can merely exist used where we expected an Option. Optionality is explicitly represented in the blazon.
And in dynamically typed languages, you cannot confuse the usage of Maybes/Options and the contained values.
Permit'due south revisit the earlier Shop
, but this fourth dimension using ruby-perhaps. The Store
class returns Some with the value if it exists, and a None if it does non. And for phone numbers, Some is for a phone number, and None is for no phone number. Thus there are 2 levels of existence/non-beingness: the outer Maybe indicates presence in the Shop; the inner Maybe indicates the presence of the phone number for that name. We have successfully composed the Maybes, something we could not practise with nil.
cache = Store.new() enshroud.ready('Bob', Some('801-555-5555')) cache.ready('Tom', None()) bob_phone = enshroud.get('Bob') bob_phone.is_some # true, Bob is in enshroud bob_phone.get.is_some # true, Bob has a telephone number bob_phone.get.become # '801-555-5555' alice_phone = cache.get('Alice') alice_phone.is_some # simulated, Alice is not in enshroud tom_phone = cache.get('Tom') tom_phone.is_some # true, Tom is in cache tom_phone.get.is_some #imitation, Tom does not have a phone number
The essential difference is that there is no more marriage–statically typed or dynamically assumed–betwixt NULL and every other type, no more nonsensical spousal relationship between a present value and an absence.
Manipulating Maybes/Options
Allow'south go along with more examples of not-NULL code. Suppose in Java 8+, we have an integer that may or may not exist, and if information technology does be, we impress it.
Optional<Integer> option = ... if (option.isPresent()) { doubled = System.out.println(option.get()); }
This is good. Merely nearly Maybe/Optional implementations, including Java's, support an fifty-fifty better functional arroyo:
option.ifPresent(ten -> System.out.println(ten)); // or option.ifPresent(System.out::println)
Non but is this functional mode more succinct, but information technology is likewise a picayune safer. Remember that pick.become()
will produce an error if the value is not present. In the before case, the go()
was guarded past an if
. In this example, ifPresent()
obviates our need for go()
at all. It makes there manifestly be no issues, rather than no obvious bugs.
Options tin be thought nearly as a collection with a max size of i. For example, we tin can double the value if it exists, or leave information technology empty otherwise.
option.map(ten -> 2 * x)
We tin optionally perform an performance that returns an optional value, and "flatten" the event.
selection.flatMap(x -> methodReturningOptional(x))
We can provide a default value if none exists:
choice.orElseGet(5)
In summary, the real value of Possibly/Option is
- reducing unsafe assumptions well-nigh what values "exist" and which do not
- making it easy to safely operate on optional data
- explicitly declaring any unsafe existence assumptions (e.one thousand. with an .become() method)
Downwardly with Nada!
Aught is a terrible design flaw, one that continues to cause constant, immeasurable pain. Only a few languages have managed to avert its terror.
If you practice choose a language with Nada, at least possess the wisdom to avoid such awfulness in your own code and use the equivalent Maybe/Option.
NULL in common languages:
Language | Zilch | Maybe | Zero Score |
---|---|---|---|
C | NULL | ||
C++ | NULL | boost::optional, from Heave.Optional | |
C# | zip | ||
Clojure | nil | java.lang.Optional | |
Common Lisp | null | maybe, from cl-monad-macros | |
F# | null | Cadre.Choice | |
Go | nil | ||
Groovy | null | coffee.lang.Optional | |
Haskell | Mayhap | ||
Java | cypher | java.lang.Optional | |
JavaScript (ECMAScript) | null, undefined | Perchance, from npm maybe | |
Objective C | nil, Nil, Nada, NSNull | Maybe, from SVMaybe | |
OCaml | option | ||
Perl | undef | ||
PHP | Zip | Perchance, from monad-php | |
Python | None | Maybe, from PyMonad | |
Red | aught | Maybe, from ruby-perhaps | |
Rust | Option | ||
Scala | null | scala.Option | |
Standard ML | option | ||
Swift | Optional | ||
Visual Basic | Zip |
"Scores" are according to:
Does not accept NULL. | |
Has Zip. Has an alternative in the language or standard libraries. | |
Has NULL. Has an culling in a community library. | |
Has Naught. | |
Programmer's worst nightmare. Multiple NULLs. |
Edits
Ratings
Don't take the "ratings" too seriously. The existent point is to summarize the state of Nil in various languages and show alternatives to NULL, not to rank languages mostly.
The info for a few languages has been corrected. Some languages have some sort of nix pointer for compatibility reasons with runtimes, but they aren't really usable in the language itself.
- Instance: Haskell's
Foreign.Ptr.nullPtr
is used for FFI (Foreign Function Interface), for marshalling values to and from Haskell. - Example: Swift's
UnsafePointer
must be used withunsafeUnwrap
or!
.
- Counter-case: Scala, while idiomatically fugitive cypher, all the same treats null the same equally Java, for increased interop.
val 10: String = goose egg
When is NULL okay
It deserves mentions that a special value of the same size, like 0 or NULL tin can be useful when cut CPU cycles, trading code quality for performance. This is handy for those low-level languages, similar C, when information technology really matters, simply information technology really should exist left in that location.
The Real problem
The more general upshot of Null is that of picket values: values that are treated the same equally others, merely which have entirely unlike semantics. Returning either an integer index or the integer -one from indexOf
is a good example. NUL-terminated strings is some other. This post focuses mostly on NULL, given its ubiquity and real-world furnishings, but just as Sauron is a mere servent of Morgoth, so besides is NULL a mere manifestation of the underlying problem of sentinels.
Interested in learning more near our software? Sign up for Lucidchart free here. Desire to join our team? Check out our careers page.
Source: https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/
0 Response to "Error Compiling Code Please Try Again Later Foo bar"
Post a Comment