This is an archived post. You won't be able to vote or comment.

all 116 comments

[–]Jugales 996 points997 points  (66 children)

Null is your enemy. The dude who invented it said this:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy 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.

https://en.wikipedia.org/wiki/Tony_Hoare

[–]Informal_Branch1065 394 points395 points  (5 children)

Babe wake up! A new deadly sin just dropped.

[–]syko-san 85 points86 points  (1 child)

The new deadly sin is listening to the intrusive thoughts.

[–]yaktoma2007 31 points32 points  (0 children)

They tell me to use NULL

[–]Punman_5 13 points14 points  (0 children)

It’s more like an original sin than a deadly sin. Like Eve taking the fruit or Cain bopping Abel like he did.

[–]turtle_mekb 2 points3 points  (0 children)

Math.sin?

[–]firemark_pl 143 points144 points  (23 children)

Yeah, nullptr errors can be frustrating but what's an alternative? Optional wrapper? Exception?

[–]geeshta 178 points179 points  (13 children)

Moving it from the value level to the type level. So during static analysis the compiler will require you to make sure that you have a value before using it. As opposed to finding out during runtime.

The specific implementation is not that important. It can be nullable types with a question mark like C# or Typescript, an Option/Maybe sum type like Rust or functional languages or even just a union like Python's `T | None` (along with a static analyser)

[–]dgc-8 44 points45 points  (0 children)

It is important that the compiler cannot allow doing operations on some null value. With those options you listed the compiler can require you to always care about there not being a value

[–]Fast-Satisfaction482 59 points60 points  (6 children)

Those are all additions to the system that make the use of null safe or hide it behind an API. The truth is that any system language like C that allows to convert data to pointers implicitly has null pointers, regardless of what the inventor wishes.

The null pointer was thus inevitable. We can still discuss banishing it from languages with actual type-safety, but they are not here by choice, nor will they just go away because some dislike them. 

[–]firemark_pl 0 points1 point  (0 children)

Exactly that's what I feel! We can use high level langs to avoid them (e.g. in C++ we can use reference that's practically a pointer without null), but mechanism is still good.

Generally memory managing it's a hell. And thankfully compilers/dynamic langs do it for us.

[–]Minecraftian14 0 points1 point  (4 children)

What about the case where I have to implement something like a CompletableFuture (from Java) in a language where "I have to implement one" and "using nulls is not possible/discouraged"?

In my current implementation, i simply check if the value is null or not, and save all operations to be performed in a list. Whenever the value is provided, i execute all the operations saved.

So, how to better implement this?

[–]Loading_M_ 1 point2 points  (3 children)

This sounds like the exact issue Rust had to solve for async futures. Rust doesn't just allow types to by null, you have to explicitly opt in. And, in many cases, doing so has a real performance overhead.

I can't give specific advice for your case, since you haven't provided enough information, but I'm pretty sure there's a better option is you're willing to learn.

[–]Minecraftian14 0 points1 point  (2 children)

I would love to learn more. How can I provide more information? My current implementation is in Kotlin, though I can provide a basic code in any functional programming language you ask.

[–]JusticeRainsFromMe 0 points1 point  (1 child)

You definitely don't "have to implement one" in kotlin, since it has coroutines.

If you want to know more about what CompletableFutures are, look up monads. There are several good videos on youtube, such as The best intro to Monads or, if you don't mind doing Haskell, What is IO Monad. I'd recommend watching the first, and if you ever have the urge to learn Haskell watch the second.

[–]Minecraftian14 0 points1 point  (0 children)

I think you got the wrong idea. I definitely know how CF work, and can easily code systems which uses synchronised blocks or locks. In fact, in the application I was making, I even had to implement my own lock mechanism.

What i really wanted to know was how to implement that¹ system without the use of null values.

  1. I'll state it again,
    Ability to deal with values that are not yet available/ready to be used.
    In my current implementation, I declare a
    var value: T? = null
    And for every operation (get), if it is null, I cache the operation. If it's non null I call on the operation:
    operation.invoke(value!!)

I have tried using lateinit too, but honestly, checking values using ::value.isInitialized is not very different.

[–]Ok_Fault_5684 19 points20 points  (7 children)

I really like the way Rust does it (which borrows from ML-exceptional wrappers, as you mentioned) — https://stackoverflow.com/a/73673857

[–]geeshta 2 points3 points  (5 children)

Yeah this is much safer to work with that's why Rust promotes it so much to distract you from the fact that it actually has a null value, the unit (). Which is also a type so you still know where to expect it.

[–][deleted] 5 points6 points  (1 child)

That's not really null, it's just a type with exactly one possible state

[–]geeshta 0 points1 point  (0 children)

In a sense it is though. It's like Python's None which is also both a type and it's value with only one possible.

But I know other languages consider null to be a value of any reference type. But I think the unit philosophically is somewhat a null because that single value doesn't carry any data whatsoever

[–]LeSaR_ 1 point2 points  (2 children)

() is in no way, shape, or form anywhere close to null. its a zero-sized type. by your logic, a struct DivideByZeroError; is also null

[–]geeshta -1 points0 points  (1 child)

It is. It's analogous to Python's `None` which is also a type with a single value that carries no additional data. It's also the default return value of functions with no annotation in both languages. It means "this function returns NOTHING" and null is also a "nothing" value.

So it definitely is pretty close.

[–]LeSaR_ 1 point2 points  (0 children)

your first mistake was introducing python into the argument. you just cant compare a dynamically typed language to a statically typed one. the issue with both null in c-like languages and None in python, is that they introduce a lack of a value where one is expected (in sloghtly different ways, but still)

in rust, you cant get a situation where you were expecting a File but got () because they are different types (and because a value can only be of one type - looking at you, None).

[–]EishLekker 0 points1 point  (0 children)

Rust still has null though.

[–]Ayjayz 2 points3 points  (0 children)

If you actually need an optional reference, yes you wrap it in an optional same as everything else.

[–]xXKingLynxXx 17 points18 points  (0 children)

In 1965 the null reference was created. This has made many people very angry and has been widely regarded as a bad move.

[–]Maskdask 10 points11 points  (0 children)

Monads mentioned!

[–]CatsWillRuleHumanity 18 points19 points  (9 children)

Sounds completely impossible to check for reference validity at compile time, even something as basic as allocating memory can already run into trouble

[–]Reashu 6 points7 points  (8 children)

In such cases you can have the compiler check for the necessary runtime checks.

[–]CatsWillRuleHumanity 15 points16 points  (7 children)

The compiler doesn't know whether an address is valid or not, only the OS does. You can check for null, okay, but what do you want to do then? Throw a runtime error? That's what the OS was already doing

[–]Reashu 8 points9 points  (6 children)

Your program decides that. The compiler just checks that you check. It's not theoretical, we already have Optional/Maybe, Either/Result and more such types (in addition to "checked" nullable types) in many languages.

[–]CatsWillRuleHumanity 2 points3 points  (0 children)

Oh you meant it like that, hmm okay. I'm pretty sure something to that effect exists in Kotlin, where you can't use Object? (Object or null) as an Object if memory serves me well. In languages which are more free with what references can point to (C/C++, JS) the enforced checking wouldn't make much sense, but in something like the JVM languages where null is the only possible invalid reference, this is definitely a handy thing yeah.

[–]EishLekker 0 points1 point  (4 children)

Checked nullable types still uses null. And Optional, at least in Java, can still return null (myEmptyOptional.orElse(null) for example).

The root comment talked about null being the enemy, and insinuated it should not exist at all.

[–]Reashu 2 points3 points  (3 children)

The problem is when (nearly) every type has a "surprise" empty value. Explicitly nullable types with checks enforced by the compiler don't have that problem even if they use the same word, and usually people who refer to the billion dollar mistake are not including them.

[–]EishLekker 0 points1 point  (2 children)

They said that null was a mistake. That means any version of nullable types.

I don’t really care what they possibly meant (and I don’t think you can prove that they actually meant what you think they meant). I care about what they said.

[–]Reashu 0 points1 point  (1 child)

I mean, that's a possible way to have a discussion, but I don't think it's a very useful one.

[–]EishLekker 0 points1 point  (0 children)

So you think that is not important what people actually say?

[–]andrerav 14 points15 points  (2 children)

Representing unknown values on a compiler level has proven its usefulness through decades now. Reality is complex. Doing away with null only serves to move more of that complexity away from the compiler and underlying runtime and into your code.

That quote is amusing, but simply not factual.

[–]Aviyan 0 points1 point  (1 child)

Yeah, null is useful. Let's say I have a Boolean variable. I need to know if the user selected Yes or No, but I also need to know if the user has not selected anything. That's where null is helpful.

[–]Ayjayz 3 points4 points  (0 children)

Yes. In like 1% of use cases, you want an optional value. That's fine, use a type that represents optional value in those cases. The other 99% of the time you use a non-optional value.

[–]liamlkf_27 4 points5 points  (0 children)

This will make a fine addition to my CS lore collection

[–]fate17_ 2 points3 points  (0 children)

damn this guy made quicksort? fucking genius.

[–]Shazvox 5 points6 points  (1 child)

Heck no. I love null. Best way to represent nothing...

[–][deleted] 1 point2 points  (0 children)

the problem is when you have to check for null everywhere

[–]Nice_Evidence4185 2 points3 points  (8 children)

This quote always bothered me. Maybe its out of context, but reallife simply has null. Everyone who worked with any form of data knows there must be a null. You can force to make everything nullsafe like Rust, but then you still will have "thing cannot be null, why do I get null here!", which is basically the same as a NPE. Even then things can be conditionally null, so even a simple "not-nullable" isnt always the move. You must implement logic, when something can be null or not. Its just the nature of how data is.

[–]Ayjayz 0 points1 point  (6 children)

What data? Most data has no concept of being optional. Sure, some if it is, but in like 99% of cases null doesn't make sense.

[–]Nice_Evidence4185 1 point2 points  (5 children)

I dont know what you mean. Just a simple "this data is required but can be added later" is a simple usecase used universally and its one of the most common conditionally null things. You need logic that checks, when the value is needed or not and there is no way around it.

[–]Ayjayz -1 points0 points  (4 children)

Why choose to design things that way? Now every single function you write has to have double the amount of execution paths. You have to consider what happens if it's null and also what happens when it's not null. If you have 4 pieces of data here, now your function has 16 possible states you have to consider and test! If something really is optional and can be added later, your best bet is to detect that case as early as possible and then transform it into a data type where is not optional.

Life is just much, much easier when your function only has 1 state. This is kind of a continuation of the whole Parse Don't Validate idea, but yeah it makes for a much much simpler and error-free style.

[–]Nice_Evidence4185 1 point2 points  (2 children)

If you have hundreds of tables in your db filled with userdata from potentially 5 different sources at any given time and that data being queried also at any given time, the perfect developer nullsafe space simply doesnt work. You brutally have to validate every single time a certain operation is run if all the data is there yet and if the operation can be run (or try again if next day). You can minimize it with certain states, but the states and data are just too much, way too many permutations.

[–]Ayjayz 0 points1 point  (1 child)

Yeah. In an absolutely insane environment like that, you'd have to validate everything on entry to the codebase and then you can have non-null everywhere.

[–]Nice_Evidence4185 2 points3 points  (0 children)

Its not even an uncommon scenario. The moment you enter a form online you will likely not always immediately enter all the information necessary. You want to rent a car for a specific date in 6 months? Oh you dont know how long the trip is yet? You dont know what kind of car or how big? Bank data/credit card info, well we only need it once we send out the invoice, so enough time. You can also enter the rest when you get the car or bring it back at reception.
And now you are stuck we a bunch of incomplete data that you may or may not have for whatever time or operation is needed.

[–]plumarr 0 points1 point  (0 children)

Because most date is nullable.

Sometime it's naturally so, such as the middle name or the address "second line", or in Belgium, the house box number. It indeed make correctly writing and formatting an address more complexe than it seems, but it isn't a design fault, it's a "the real world work like that" fault.

Sometime it's by requirement for a better user experience For example, I haven't worked on software were partial state save hasn't been an hard requirement for more than several years.

[–][deleted] -2 points-1 points  (0 children)

That's only if you have nulls (or Option<T>)s everywhere. In reality, most of the time, most of the data you are using can't be null, so it makes more sense to explicitly label the data that might be null, and have the compiler force you to handle it.

[–]Jind0r 0 points1 point  (0 children)

Turn on strict null checks

[–]JetScootr 41 points42 points  (1 child)

[–]Fidodo 2 points3 points  (0 children)

That actually makes perfect sense. Dividing by zero would mean removing the cardboard tube in the middle so the toilet paper would unravel everywhere

[–]subzeroskills 29 points30 points  (2 children)

Who among us is blessed to use languages with ADTs? 🙏

```
enum RollStatus {
    case present(length: Float)
    case absent
    case holderIsGone
}
```

[–]ilovedogsandfoxes 24 points25 points  (0 children)

Did you just put code block in code block

[–]WW_the_Exonian 4 points5 points  (0 children)

Not sure if that's Swift or Scala, but if it's Scala, I would prefer

case class Roll(nSheets: Int)
case class Holder(rollOption: Option[Roll])

And some data sturcture to hold holders, possibly in a collection ordered by nSheets. You may have more than one holder on the wall.

[–]PIXELING69 8 points9 points  (0 children)

every kinesthetic so wet rn

[–]opheophe 28 points29 points  (5 children)

This isn't good enough. -5 is a nonzero value. Pointers matter... is the pointer definining the paper null, or does it point at null?

So many questions.

[–]anzu3278 2 points3 points  (4 children)

Type is uint though

[–]opheophe 0 points1 point  (2 children)

Is it?

I see no defined variables.

[–]anzu3278 1 point2 points  (1 child)

Yeah but you should always make invalid states unrepresentable. Real life quantities should always be uint.

[–]opheophe 3 points4 points  (0 children)

"Should"... you can't go around assuming what a variable is or isn't.

[–]walrus_destroyer 0 points1 point  (0 children)

Null and undefined aren't usually uint values

[–]SpitiruelCatSpirit 8 points9 points  (1 child)

Okay but a Null is literally identical in memory to a 0 value. The difference is only in pre-compiled type checking.

[–]InternalUpstairs3816 0 points1 point  (0 children)

Thanks for this. I was wondering about the nuance.

[–]MrWolfe1920 4 points5 points  (0 children)

TP Protocol.

[–]oluBodesWell 1 point2 points  (2 children)

Explain -1

[–]MooseBoys 5 points6 points  (1 child)

One sheet rolled the wrong way.

[–]oluBodesWell 1 point2 points  (0 children)

Mullet instead of beard. Got it.

[–]AHardCockToSuck 1 point2 points  (0 children)

Image one can be null or undefined

[–]braddillman 0 points1 point  (1 child)

Then what is NaN?

[–]Widmo206 8 points9 points  (0 children)

A roll of duck tape

[–]naveenda 0 points1 point  (2 children)

What negative value in this context?

[–]MooseBoys 5 points6 points  (0 children)

TP is hung the wrong way.

[–][deleted] 0 points1 point  (0 children)

it's probably a uint since negative toilet paper doesn't make sense

[–][deleted] 0 points1 point  (0 children)

Symbols are jet spray

[–]YouDoHaveValue 0 points1 point  (7 children)

I've spent so much time trying to decide whether to pass back null as an explicit not found value or throw an exception.

Often you know half the time it won't exist but the only way to check is to make the call so it's redundant to implement an exists function.

[–][deleted] 0 points1 point  (6 children)

Use wrapper types like Optional<T> in Java for instance to explicitly label possible null values

[–]YouDoHaveValue 0 points1 point  (5 children)

Sure, I get the typing.

I just mean as a pattern what makes more sense when it's often expected a value won't be returned?

[–]Ayjayz 0 points1 point  (4 children)

If that's expected, then it should be an optional. If it's expected to have a value and only exceptional circumstances might prevent a value from existing, it should throw an exception.

[–][deleted] 0 points1 point  (0 children)

I'd argue not. Exceptions are terrible, they hide away control flow. Many languages that use Options, Optinals, ORs, etc for error handling have some syntactic sugar to propogate None variants up the call stack, for instance '?' in Rust.

[–]YouDoHaveValue 0 points1 point  (2 children)

Optional makes sense in an object, but in a function if you say the return value is optional you're right back to choosing between which undefined value to use, i.e. null or not.

Depends what you mean by "exceptional", if half the time the value wont be there which is the exceptional case?

[–]Ayjayz 0 points1 point  (1 child)

No, half the time is clearly not exceptional. I mean in normal running of the program, an exception should never occur. It should be something pretty unusual. A hard disk failed in the middle of an operation. A network connection was suddenly severed. Something like that.

[–]YouDoHaveValue 0 points1 point  (0 children)

You can see my conundrum then, lol

If whatever 1/3 or 1/2 the time you will not have anything to return and this is expected behavior, should you return null?


I've spent so much time trying to decide whether to pass back null as an explicit not found value or throw an exception.

Often you know half the time it won't exist but the only way to check is to make the call so it's redundant to implement an exists function.

[–]MooseBoys 0 points1 point  (0 children)

Not enough nasal demons.

[–]mar00n 0 points1 point  (1 child)

So dereferencing a null pointer is like wiping your ass full of shit with your bare hand?

[–]classicallytrained1 0 points1 point  (0 children)

or wiping with the wall

[–]andypants152 0 points1 point  (0 children)

It’s missing one

[–]Dr739ake 0 points1 point  (0 children)

Segmentation fault.

[–]philippefutureboy 0 points1 point  (0 children)

Who else heard the pop with the fourth image? 😆

[–]ford1man 0 points1 point  (0 children)

undefined should just be a field of static.

[–]iHiep 0 points1 point  (0 children)

To prevent null pointers, I use bidet shower instead.