
What is wrong with NULL - mjswensen
https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/
======
geofft
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.

Python's problem, meanwhile, is that any parameter could be _any_ 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. :)

Also, to quibble a bit: Rust's std::ptr::null() is just a raw pointer, and
isn't actually _usable_ 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.

~~~
dradtke
I also think that Go did pretty well to fix NULL. It didn't get rid of it
completely, but it (nil) is only valid for pointers and interfaces, so it's
not possible to use it in place of a string, integer, etc. It's not as
bulletproof as Rust or Haskell, but something as simple as disallowing nil
strings can make a world of difference.

~~~
tines
This is what C does, so I don't think Go deserves much credit for innovation
on this point...

------
bronson
_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._

That could be the greatest intro sentence ever seen on Hacker News.

~~~
protonfish
Pretty good, but I've never had a problem with an _XMLHttpRequest_ being
inconsistent. Everything else seems spot-on though.

~~~
iron_ball
I think they're joking about the capitalization of the name.

~~~
paulddraper
Yeah. That's what I was going for ;)

~~~
protonfish
I get it now! Duh.

------
tolmasky
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):

    
    
       if let roomCount = john.residence?.numberOfRooms {
          println("John's residence has \(roomCount) room(s).")
       } else {
          println("Unable to retrieve the number of rooms.")
       }
    

Or from this blog post's own example:

    
    
       option.ifPresent(x -> System.out.println(x));
    

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.

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.

~~~
fixermark
This is a concern, but the advantage to Maybe is it makes this concern very
explicit. SML/NJ code will not compile if you don't have a binding to handle
the None for an option 'a type. The programmer does of course then have the
option of doing

None => raise Error

but practically speaking, when deciding it's time to make your code more
robust, it's a lot easier to text search for instances of "None => raise" than
to search for the absence of proper handling of the possibility of a null arg
or return value.

~~~
paulddraper
Exactly. You can still get errors due to things not being there. But your
assumptions are _explicitly_ stated.

You see None => raise Error, and a little alarm goes off in your head, and you
look around for a reassurance that we _really_ want to do that.

In contrast, you don't give x.toUpperCase() a second glance.

------
kazinator
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!"

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.

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.

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 _want_ 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.)

~~~
weaksauce
ruby nil is similar in that it's an instance of the Nil class. though, calling
a method on nil that isn't there is a common error.

I like how swift handles things. strong type system that makes it a little
painful to do things unsafely. guarantees some things that make it a powerful
and more safe language than objective c. I think they lost a few dynamic
patterns though in the transition but that's just what i recall from a blog
post and the details of it are a little fuzzy.

~~~
archagon
I agree, Swift's optionals are really great. But I also like Objective-C's
nil: you can send messages to it and it won't do anything. That too solves a
ton of problems with NULL.

~~~
kazinator
What solves problem with null is if you can send messages to it, _and have it
do exactly what you want_ depending on the nature of message.

------
carsongross
We don't need to get rid of null, we need _more, type-specific and context-
carrying_ 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.

    
    
      class Connection {
    
         //Error instances
         BAD_ADDR("The given address was not correct...")
         ...
    
      }
    

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.

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.

~~~
geofft
If you have a language with enum types (Haskell, ML, etc., or any of the
languages inspired by them like Rust, Scala, etc.), that's straightforward. In
e.g. Rust, you'd have

    
    
        enum Connection {
            ValidConnection {fd: RawFd, address: SockAddr, ...},
            BadAddr,
            BadPort,
            ...
        }
    

In fact Rust's representation of the maybe type is just a generic

    
    
        enum Option<T> {
            Some(T),
            None,
        }
    

so all you're doing is getting rid of the two layer Option<Connection> syntax
and making it a single Connection, so you're not writing Option<this>,
PossiblyBadAddress<that>, etc. everywhere.

Then all your call sites can do one of two things. Either they can check for
specific errors, like

    
    
        match conn {
            Connection {fd, address, ..} => write(fd, ...),
            BadAddr => /* handle error */,
            BadPort => /* handle error */,
        }
    

or you can write a simple function that throws an exception, if you really
want an exception-handling style:

    
    
        impl Connection {
            fn get_fd(&self) -> RawFd {
                match *self {
                    ValidConnection {fd, ..} => fd,
                    BadAddr => panic!("The given address was not correct"),
                    BadPort => panic!("The given port was not correct"),
                }
            }
        }
    

and then call conn.get_fd(). For a simple command-line app, panicking and
aborting is pretty much what you'd want to do anyway. For a test suite, panics
are caught per test case (and you can even test that a function _does_ panic
with a given message), so it's very testable.

~~~
carsongross
You shouldn't have to specify the error states in each method, this is why
what I'm advocating is different than a simple enum:

\- You don't need to define a constructor to take the error string

\- You don't need to implement method dispatch (all methods of an error state
instance automatically throw, like null does)

Enums are close, but not quite right.

~~~
geofft
Hm, I think what you might want is an enum / sum type, but with the ability to
state that a particular variable is statically a particular variant of that
enum. So, for instance, I can write fn serve(conn:
&Connection::ValidConnection), and it's a type error to pass a generic
unchecked Connection to it. Then serve() can go and call other functions that
take a ValidConnection without doing any further error handling.

You might, for convenience, have a single method that turns a Connection to a
ValidConnection, or else throws, and you can encode your error messages in one
place in this method.

I _think_ this proposal permits such a thing, although I haven't read it
closely yet:
[http://smallcultfollowing.com/babysteps/blog/2015/08/20/virt...](http://smallcultfollowing.com/babysteps/blog/2015/08/20/virtual-
structs-part-3-bringing-enums-and-structs-together/)

------
aurelian15
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.

C++ has an inbuilt alternative: references. References in conjunction with the
Null Object Pattern have solved my problems until now.

References simply cannot be null -- and I generally do not use raw pointers in
my code unless some library forces me to.

~~~
_yosefk
A null pointer can be dereferenced into a "null reference" which can be passed
around, however. Theoretically UB, in practice, typically represented
identically to the null pointer and behaves the same when used. Still helps in
that the null bug root cause can hide in less places (modulo memory
overwriting which adds root causes unimaginable in Java)

------
paulddraper
The title originally matched the blog post ("The worst mistake of computer
science"), but then it was changed by a moderator.

Maybe it could have been a less drastic change?

"NULL, the worst mistake of computer science"

or

"The worst mistake of computer science: NULL"

------
bcheung
Here is what is wrong with Option.

option.ifPresent(x -> System.out.println(x));

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?

Why not just do:

x && System.out.println(x)

or:

System.out.println(x) if x

Or if you want to skip over the rest of the logic if it is null:

return unless x

System.out.println(x)

I don't see why I need to introduce a new type system and complicate things.

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?

option.orElseGet(5)

So instead of a memory lookup I now need the overhead of an entire function
call?

Why not just do:

x ||= 5

Or better yet, just put it in your function declaration as a default value:

myFunc = (x=5, y, z) -> ...

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.

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.

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.

~~~
dragonwriter
> The one benefit I see is type safety but it is certainly not easier

Type safety is one benefit, but composability is an important benefit (as, you
know, is extensively discussed in the source article.)

~~~
bcheung
Composability comes from functions, macros, and inheritance. What does that
have to do with types?

"In fact, composibility is really the fundamental issue behind many of these
problems. For example, the Store API returning nil for non-existant values was
not composable with storing nil for non-existant phone numbers."

That's only because you invented some abstraction that got in your way in the
first place. NULL is a perfectly acceptable value in dynamic languages and
even in most document store databases.

~~~
paulddraper
This is not contrived.

(1) A cache returns something or NULL. It's generic; i.e. implementation
doesn't care what it is storing: integers, strings, etc.

(2) A particular value of interest may be NULL or non-NULL.

But now I can no longer use my generic cache and my values of interest
together.

A very real example of this is Java's Map interface. It's completely up to
implementation on how they handle null, making interpreting null results
difficult.

While often you use NULL and everything works, you're still limited to a
certain set of circumstances. You can't pick up thing A and thing B and just
use them together. Hence non-composable.

~~~
bcheung
You're conflating the meaning of NULL in that case. That's not a problem of
NULL, you're using NULL to represent multiple different things. You can use
exceptions to handle that scenario.

    
    
      cache = {}
      getFromCache = (key) ->
        return cache[key] if cache[key]?
        throw "not found"
      
      doSomethingWithValue = (key) ->
        try
          value = getFromCache(key)
        catch
          value = getValueFromNonCache(key)
        return _doSomethingWith(value)

~~~
dragonwriter
You can, but resorting to exceptions for what may not be an exceptional
circumstance is a workaround for not being able to think of a way to
communicate results correctly through the return value. Which illustrates the
need here as effectively as the ambiguous use of null in the article.

~~~
bcheung
You don't like exceptions so your solution is creating 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?

------
Animats
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.

Rust doesn't have null, but it has Option<T>, which is often syntactic sugar
for null.

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.

~~~
nhaehnle
_The new move semantics use null references for things moved from._

No. When you move the contents out of somewhere like an std::vector instance,
that object is left in an undefined but valid state. You're free to continue
working with that object (though the only meaningful thing you can usually do
without undefined behaviour is destruction or the equivalent of _clear_ ).
That's very different from null references.

~~~
Animats
Right; you can't have an array of references, and it's "unique_ptr", not
"unique_ref". But pointers do get set to null.

If p1 is a unique_ptr, and you do

    
    
        p2 = std::move(p1);
    

p1's pointer is set to null. Further uses of p1 will fail, or crash, or
something.

    
    
        p1.get()
    

returns the underlying pointer or null, apparently escaping the unique_ptr
protection.

It's far inferior to Rust's borrow checker.

------
PaulAJ
This mistake is fixed in Haskell.

~~~
kinghajj
Rust, too!

Edit: Btw, I disagree with how the article categorizes Rust in comparison to
Haskell. It shows that Rust has std::ptr::null, but neglects the fact that
Haskell has Foreign.Ptr.nullPtr. Either both should be "5 stars" or both
should be "4 stars".

~~~
paulddraper
Author here. I didn't know that about Haskell.

Admittedly, the "rating" is pretty rough, maybe even a bad idea.

I've seen std::ptr::null more than I ever have Foreign.Ptr.nullPtr.

But really, both are usually used for compatibility with external
libraries/programs/runtimes, not for idiomatic language programming.

Both great languages in my book :)

~~~
bjz_
> I've seen std::ptr::null more than I ever have Foreign.Ptr.nullPtr.

Probably because we need to work with the C API a great deal right now, but
that should change as more and more things are written in Rust. Still, the
unsafe boundary helps by removing the ability to dereference a raw pointer in
safe code - something that Haskell doesn't have.

------
xamuel
>NULL is a terrible design flaw, one that continues to cause constant,
immeasurable pain

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.

~~~
_Ogre
It is vastly more destructive than IP v4. This affects end users directly,
every single day. The number of times applications have crashed due to NULL
related errors is probably in the tens or maybe hundreds of billions. Each
time is an interruption of people's work and in some cases it destroys hours
of work.

------
greggyb
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:

    
    
        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)
    

Running this will give you back a two-row table, containing 1 and 2, but the
NULL is excluded from both WHERE conditions.

The naive expectation is that the combination of a condition and the NOT of
that condition cover all possible circumstances.

~~~
paulddraper
I thought of including SQL NULL. Unfortunately, I felt I would do such a poor
job of enumerating the problems or describing them in a somewhat comprehensive
way, I didn't even try.

------
ursus_bonum
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.

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 _any other_ pointer could cause unlimited damage.

~~~
paulddraper
To quote the article:

> This is a bit different than the other examples, as there are no pointers or
> references. But the idea of a value that is not a value is still present, in
> the form of a char that is not a char.

It has nothing to do with the similarity in name (NULL / NUL). As you said, it
could be terminated with $. The similarity is that they are both sentinel
values. NULL is a sentinel for types; NUL is a sentinel for char arrays.

In both cases, they create exceptional and non-composable situations.

Good explanation here:
[https://www.reddit.com/r/programming/comments/3j4pyd/the_wor...](https://www.reddit.com/r/programming/comments/3j4pyd/the_worst_mistake_of_computer_science/cumbius)

~~~
ursus_bonum
I know the author _claims_ that there's a meaningful similarity beyond the
names. But that's where I disagree.

I get that a NULL pointer is in some meaningful way not a pointer. Because it
literally isn't pointing to anything. It is not the case that NUL is a char
that is not a char. NUL is a perfectly good char. _Some_ functions treat it
specially, but other than that it's just a regular char.

Even the sentinels similarity is quite weak. NULL is a sentinel that indicates
"no valid object" whereas NUL is a sentinel that indicates the end of a
perfectly valid object. Notice how we're not having this discussion about '\n'
or the whitespace chars in general, even though they're also treated as
sentinels by some functions.

------
vbezhenar
Many modern languages (Kotlin, Rust, Swift) handle this problem well.

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.

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.

------
jhallenworld
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.

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.

------
51Cards
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.

------
sampo
> If x is known to be a String, the type check succeeds; if x is known to be a
> Socket, the type check fails.

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?

------
SamReidHughes
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 _pretend_ that it can't exist, and
that uninitialized variables have a garbage value.

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.)

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 _can 't_ compare it for equality and any
attempt to inspect its value results in an exception.

~~~
Strilanc
> _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 can 't compare it for
> equality and any attempt to inspect its value results in an exception._

That's just a different kind of null. You still have the original problem:
this thing is declared as a T, but sometimes it's not a T, so you have to
inspect how the value is used before you can conclude whether it's a T or not.

The correct solution is to structure the language so access can't occur before
initialization. For example, you could place severe constraints on
constructors so they must initialize all the fields and do nothing else until
that's done. Think initializer lists from C++ (but stricter), or tagged unions
from Haskell.

~~~
SamReidHughes
That is not an option. Things like arrays need to be allocated with a default
initialization and then filled.

~~~
Strilanc
You can absolutely work around that:

\- Require an explicit initial value to be provided.

\- Or require that a collection/iterator/generator of values be provided (and
fail if it's not large enough).

\- Or have a built-in ArrayBuilder<T> type that accumulates all the needed
values before allowing you to retrieve the array.

\- Or make your language dependently typed, and change the signature of
Array.get to take both an index and a proof that said index has been
initialized.

~~~
SamReidHughes
\- Require an explicit initial value to be provided.

Let's say you want an array of FileHandles. Under this proposal, you'd need to
create an "empty" FileHandle value that you can use to fill arrays with and
such. You'd have to create some "empty" state for every type. This is worse
software engineering than having the possibility of a null pointer exception.
(Also, you couldn't write generic code to implement an ArrayList<T>
efficiently without some way to generically default-construct the T to fill
most of the array entries, or the constructor would have to take a filler
value.)

\- Or require that a collection/iterator/generator of values be provided (and
fail if it's not large enough).

This is a needlessly complicated way to use an array, and it still has the
same downsides.

\- Or have a built-in ArrayBuilder<T> type that accumulates all the needed
values before allowing you to retrieve the array.

And how do you implement your own ArrayBuilder? What if you need something
with a slower growth rate, or some other data structure that you could build
out of an array with uninitialized elements, such as a deque?

\- Or make your language dependently typed, and change the signature of
Array.get to take both an index and a proof that said index has been
initialized.

A far worse alternative.

Late edit: Generally speaking I think you're completely missing the problem of
null pointers. The problem is not that a class of error exists. It's that
people try to use null values in surprising ways in their APIs. If you make
the null value impractical as a sentinel, you remove the mismatch between what
programmers expect about whether a value can be null.

------
feedjoelpie
Another reason that, even despite sharing a lot of syntax with PHP, Hack is
one of my favorite languages:

[http://docs.hhvm.com/manual/en/hack.nullable.php](http://docs.hhvm.com/manual/en/hack.nullable.php)

------
contravariant
As a mathematician I still think making "-1 % 7 == 6 % 7" evaluate to false is
worse.

~~~
paulddraper
So you like Python.

------
ant6n
For statically typed languages this is definitely an issue, but for dynamic
languages less so. In Python, I wouldn't use an optional value,

    
    
      x is None

seems to be just fine. I'm still waiting for

    
    
      std::optional

for C++.

~~~
lmm
It's only not an issue in dynamic languages in the sense that you have the
same issue anyway without null because you have no type safety at all.

~~~
kazinator
You have a ton of type safety in Python, which is one reason why Python
programmers are cranking out large bodies of working code representing all
kinds of applications that Just Work.

"No type safety at all" means you can misuse a value of one type using an
operation that is appropriate for a different type, and some nonsensical
behavior silently occurs (or perhaps some nonportable behavior that you
sneakily intended). This characterizes assembly languages, certain machine-
oriented languages like BCPL, and some immature dynamically typed languages
which omit run-time-type checks (the type information is there, but not
checked, so that string_length happily tries to operate on an integer, so that
a type problem occurs in the interpreter's kernel itself.)

~~~
lmm
Python has some safety checks that are missing in other languages, and this is
a good thing. But it's misleading to call them "type safety", because they
aren't connected to anything that can be meaningfully called a type system -
all those checks happen at the value level (e.g. you can make a particular
integer amenable to string_length, by monkeypatching the relevant methods onto
it).

------
ffn
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:

if ( user.getScrollPosition() ) { whatever(); } else { die(); }

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.

~~~
bcheung
Why would you check the value instead of the presence of the value? Rookie
mistake.

user.getScrollPostion() != undefined

or in CoffeeScript just use:

user.getScrollPosition()?

Arrays have various types of ints and floats of various word lengths and
signs.

[https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Type...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Typed_arrays)

------
njharman
> 4\. NULL makes poor APIs

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.

~~~
AndyKelley
When I look at my coworker's code, or an open source project's code, there is
going to be usage of NULL. Your strategy is not pragmatic. The problem with
language features is that people use them.

------
SNvD7vEJ
Reading this article is like listening to a republican arguing against taxes.

------
duaneb
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.

Though, I think uninitialized variables or memory might be just as bad.

------
arielby
NUL-terminated strings aren't _that_ bad:

* unlike Pascal-style strings, they can be usefully sliced, especially if you can modify them strtok-style.

* unlike (ptr,len) "Modern C buffers"/Rust-style strings, references to them are pointer-sized, and they can be used as a serialization format.

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.

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.

~~~
Strilanc
> unlike Pascal-style strings, they can be usefully sliced, especially if you
> can modify them strtok-style.

Slicing Pascal-style strings is also easy and constant-time: just track the
buffer, offset, and length of the slice of characters you want. Java used to
do it implicitly whenever you called `substring`.

> _unlike (ptr,len) "Modern C buffers"/Rust-style strings, references to them
> are pointer-sized, and they can be used as a serialization format._

Every C method that takes a character buffer either a) has a corresponding
length parameter or b) is avoided because of the security risks. In practice
this means that C also stores the length information, just on the side instead
of combined into a struct with the buffer.

~~~
arielby
> Slicing Pascal-style strings is also easy and constant-time: just track the
> buffer, offset, and length of the slice of characters you want. Java used to
> do it implicitly whenever you called `substring`.

That's just coercing into a "modern C buffer" and slicing it. It has the
disadvantage that coercion is not equality or subtyping - i.e. you will have
to do lots of wrappings and unwrappings in mixed code.

> Every C method that takes a character buffer either a) has a corresponding
> length parameter or b) is avoided because of the security risks. In practice
> this means that C also stores the length information, just on the side
> instead of combined into a struct with the buffer.

You are surely talking about the buffer's capacity, not the string's length.
These are distinct concepts. Anyway, functions that only _read_ strings, and
structs that only store them read-only, aren't interested in the capacity of
any buffer.

Anyway, C strings aren't responsible for the fixed-size buffers of Cold War-
era code - _that_ code uses fixed-size buffers for _everything_. Their main
claim to fame is their popularity in parsing code, which is edge-case- and
bug-prone.

------
foota
I'm not a c++ expert, but doesn't

    
    
      char *myChar = 0;
      std::cout << *myChar << std::endl; // runtime error
    

compile because it's undefined behavior?

~~~
kevinchen
the point is that it's a straightforward-looking piece of code that has really
strange behavior.

------
pmelendez
In case the author is reading this thread:

The entry for C++ in the final tables is:

    
    
        C++  | NULL  | boost::optional, from Boost.Optional 
    

It does ignore that C++ has nullptr since C++11

------
njharman
> if (str == null || str == "")

Why not have null (and emptystring) eval to false? And

    
    
        if not str:
           str = 'wordup'

------
derricki
The best phrase from the article: "confusing sources of sloppy nullish
behavior."

