60

This comes with a debate with my colleague that I'm using null as an object initial state.

type Value = Node | null
const [value, setValue] = React.useState<Value>(null)

function test(v: Value) {
  if (value === null) // Do something
  else // Do something
}

I'm curious if null is a mistake in this case. What alternatives are there to represent an object that is not (yet) initialized? And how is this solved in languages that don't support null pointers?

Note: This question is similar to If nulls are evil, what should be used when a value can be meaningfully absent? - the difference is that that question is about cases where a value is truly absent/not relevant - whereas here the question is about a value where initialization has been delayed, because the value will be produced/retrieved later.

sleske
  • 10,095
  • 3
  • 29
  • 44
Mengo
  • 579
  • 1
  • 4
  • 7
  • 9
    [Sharing your research helps everyone](https://softwareengineering.meta.stackexchange.com/questions/6559/why-is-research-important). Tell us what you've tried and why it didn't meet your needs. This demonstrates that you've taken the time to try to help yourself, it saves us from reiterating obvious answers, and most of all it helps you get a more specific and relevant answer. Also see [ask] – gnat Jul 26 '20 at 22:19
  • 2
    This is not helping, I only know language that support null pointers and I think it's useful in many cases as I demonstrate in my example code. I'm asking to a field I'm not familiar of. – Mengo Jul 26 '20 at 22:24
  • 14
    `null` is only a mistake if you don't know how to deal with it correctly. One way to deal with it correctly is to make sure all of your object references always have a value. Hence, your colleague's argument. – Robert Harvey Jul 26 '20 at 23:18
  • ths solution is use Swift :) – Fattie Jul 27 '20 at 18:41
  • 2
    I think if *null* is a built-in concept of your language you cannot get rid of it and have to deal with it. Your language might not offer something like *Optionals*. But you can aim for making sure that null checking, if it cannot be avoided, will only happen at a single place in your code rather than having null checks on the same object all over the place. For instance, if you have to use some library which will return *null* you can wrap that library access with your own code, do the null checking there and make sure other parts of your code have to do this as well. – Andreas Hütter Oct 11 '20 at 05:52
  • 1
    And for your own code you can replace return of null with some custom *Null Object*. For instance, if you have some price calculation logic where you can have discounts or no discount, I often see code with a check for the discount object being null or not in different places. In this example this could be avoided if you have some *NoDiscount" class instead of returning null which will also provide a discount which is simply zero. If this approach is feasible and is worth the (from my experience still small effort in most cases) effort of course depends on your situation. – Andreas Hütter Oct 11 '20 at 05:57
  • 1
    This should be reopened because the questions are substantially different. The other question is about an object that genuinely doesn’t exist. This one is about lazy initialisation, caching etc. – gnasher729 May 04 '21 at 04:18
  • @gnasher729: I agree. I edited to clarify. – sleske May 04 '21 at 07:05
  • @gnasher729 Thanks, this edit is exactly what I meant, the initial value IS empty, and empty is a valid state of the object. – Mengo May 05 '21 at 06:28
  • Don't forget to have code reviews done by human developers. They are better than any software tool. – Basile Starynkevitch Jul 04 '21 at 13:37

10 Answers10

112

The problem isn't null itself. It's the implicit nullability of all object references, as is the case in Java, Python, Ruby (and previously C#, although that picture is changing).

If you have a language where every type T really means T | Null. Suppose you had a system that takes such a nullable reference at its entry point, does some null checking, and wants to forward on to another function to do the "meat" of the work. That other function has no possible way to express "a non-null T" in Java (absent annotations), Python or Ruby. The interface can't encode its expectation for a non-null value, thus the compiler can't stop you from passing null. And if you do, fun things happen.

The "solution" (or "a solution", I should say), is to make your language's references non-nullable by default (equivalently, not having any null references at all, and introducing them at the library level using an Optional/Option/Maybe monad). Nullability is opt-in, and explicit. This is the case in Swift, Rust, Kotlin, TypeScript, and now C#. That way, you clearly distinguish T? vs T.

I would not recommend doing what you did, which is to obscure the nullability behind a type alias.

Alexander
  • 3,562
  • 1
  • 19
  • 24
  • This is very helpful! I'm using Typescript and I have another function doing the "meat" work by only accepting `Node` type. eg. `diff(a: Node, b: Node)`. This top level nullable is only used when user has to provide two values. – Mengo Jul 26 '20 at 22:34
  • @Mengo Isn't there a question mark syntax for nullable types in Typescript, like `Node?`? If there is, use that. If there isn't, I would suggest either explicitly `Node | null`, *or* a better name than `Value`. "Value" is one of those absolutely useless words in identifiers, like "data", "info", etc. There is else BUT values. – Alexander Jul 26 '20 at 22:51
  • In TS `Node?` means `Value: Node | undefined`, this is what my colleague suggest to change. But `undefined` type in JS is preserved for item not exist. ie. `object[key] === undefined` or `array[-1]` if item not exist. So I feel if we use that type it'll not distinguish item existence. – Mengo Jul 26 '20 at 22:58
  • 2
    @Mengo I don't know the conventions here, so I can't help. But definitely steer clear of vague nothingness like "Value" – Alexander Jul 27 '20 at 00:58
  • Definitely, pretty bad at naming things, thanks for you help. :) – Mengo Jul 27 '20 at 01:04
  • 6
    Good answer!  And coming from a Kotlin background, I'd say that in addition to able to _identify_ nullable types, it's also a Very Good Thing™ for the language to provide easy ways to safely _handle_ nullable values, and for compiler to _check_ for and disallow unsafe null accesses.  (In such a language, nulls are arguably superior to most of the alternatives.) – gidds Jul 27 '20 at 10:07
  • I'm assuming you haven't seen [PEP 484](https://www.python.org/dev/peps/pep-0484/). `typing.Optional[T]` / `typing.Union[T, None]` are not the same as `T` in Python. – user368564 Jul 27 '20 at 11:16
  • 1
    I'm sorry, I am a bit confused; surely python does not have a null type? An object is an object, you can't have null pointers.. what am i missing? – Ant Jul 27 '20 at 11:31
  • 1
    The issue is not the nullability; the NPE is not the mistake but the result of incorrect programming. If you have a reference to an object that has not been initialized with the right values due to incorrect programming, you have the same problem that with a NPE but then your program even does not know that it is in an incorrect state. – SJuan76 Jul 27 '20 at 11:38
  • 5
    @Ant Python has `None` which, while technically an object itself, is functionally equivalent to null in that any undefined objects will default to being `None`. – Sean Burton Jul 27 '20 at 12:34
  • @SJuan76 "the NPE is not the mistake but the result of incorrect programming" This line of reasoning could be used to argue "well stack smashing is not the mistake but the result of incorrect programming", it's like well, sure... but we can do something about that and design that problem out of existence. – Alexander Jul 27 '20 at 13:23
  • 2
    @Mengo I think you’re restricting the meaning of `undefined` a bit too much—it’s good to give it *some* restrictions in semantic usage, though the language doesn’t really enforce anything (Javascript’s fault), but you go too far. A Javascript variable that is declared by not initialized also has type `undefined`, which makes it perfect for your usage in the title. A `null` value should be “this space left intentionally empty” values. Your colleague is correct to prefer `undefined` over `null` in the title circumstance, I believe. – KRyan Jul 27 '20 at 13:26
  • Anyway, though, I would argue that Typescript’s type system can make a type alias desirable here, though. I see nothing wrong with aliasing `Node | null`. Because Typescript uses structural typing, the alias and the underlying meaning are entirely equivalent, which can be useful (I realize nominal type systems often treat aliases that way too). And if the alias is only used locally, I could even see a name as generic as `Value` being acceptable. – KRyan Jul 27 '20 at 13:30
  • @KRyan If the scope is small enough fair enough. But `Node | null` really isn't that much longer than `Value`, so I don't see much of a win. I wonder if `Node?` is more appropriate – Alexander Jul 27 '20 at 13:52
  • 3
    Great answer. One thing to mention/keep in mind though is that in languages like C# or TypeScript, which (now) have the concept of explicit null references but exist in an ecosystem where that is not generally the case (.NET or JavaScript respectively), the usefulness is greatly diminished as every public interface still has to deal with nulls even when the type declaration forbids it. This highlights how bad the "billion dollar mistake" really is: you cannot fully escape it unless you start from scratch. – Simon Lehmann Jul 27 '20 at 14:17
  • 5
    @SimonLehmann yeah, good point. It's similar in Kotlin. Great interop with Java APIs, but a lot of libraries aren't annotated with nonnull annotations, so you're really missing out a lot. Having `@nonnull` and `final` all over the code base isn't great, either. I've grown to be strongly convinced that languages should default to non-null references by default, and read-only variables by default. Nullability and mutability should be opt-in. – Alexander Jul 27 '20 at 14:21
  • 1
    If anyone's curious, [this is how & when C# changed](https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references). – J.G. Jul 27 '20 at 14:25
  • 1
    'That other function has no possible to express "a non-null T" in Java (absent annotations), Python or Ruby.' Eh? Types are by default not-nullable in Python, if you declare types at all. `None` is allowed only with the addition of `typing.Optional`: https://gist.github.com/bitglue/edec3b65688adcb6a4776094284de1c2 – Phil Frost Jul 27 '20 at 14:35
  • 4
    @PhilFrost Aren't those type annotations for human consumption only, and not actually enforced? – Alexander Jul 27 '20 at 14:42
  • @Alexander-ReinstateMonica They are both. When running the Python program type annotations are ignored. But there are linters like [mypy](http://mypy-lang.org/) as well as on the fly checkers like the one in the PyCharm IDE. – besc Jul 27 '20 at 15:19
  • @besc Hmm, that's kind of a grey area, not sure how to summarize that succinctly in my answer – Alexander Jul 27 '20 at 15:22
  • Yeah, you wouldn’t use `Value` to save typing on `Node | null`; the primary usage of it that I would consider is for future-proofing: maybe you’re using a `Node | null` value because the you have an unreliable data source. Maybe you hope to someday improve things so that it can no longer be `null`. And maybe you need that `Value` annotation all over the place, which would be a pain to change all of them at once. That’s a situation where I’d use it. – KRyan Jul 27 '20 at 15:32
  • How about [using an immutable empty object](https://www.youtube.com/watch?v=MBWAP_8zxaM&t=12m48s) instead of "null"? – Peter Mortensen Jul 27 '20 at 16:07
  • @PeterMortensen You can suggest an edit. I fixed this instance though – Alexander Jul 27 '20 at 16:08
  • @Mengo Whilst `a['x'] === undefined` cannot distinguish between `a = {x: undefined}` and `a = {}`, `'x' in a` and `a.hasOwnProperty('x')` can (which is best depends on whether inherited properties count), so using `undefined` in this way does not prevent you checking for presence of properties. – James_pic Jul 27 '20 at 16:29
  • 1
    @Alexander-ReinstateMonica I think you miss the main point by Juan: NPE is just a symptom, if we move from NPEs to non-null values with bogus contents, that's not really better (arguably worse since it's more difficult to track down). Arguing that non-nullable types fix that problem too is more involved than the above answer. – ljrk Jul 27 '20 at 16:36
  • 2
    I'm not suggesting that "non-null values with bogus contents" is better. I agree that NPE is merely symptom of an underlying problem, but that underlying problem isn't just "bad programming", it's nullable-by-default type systems that don't let you safely express "must not be null" references that can be safely referenced. – Alexander Jul 27 '20 at 16:59
  • 1
    const by default would have been a helpful norm as well. – Justin Meiners Jul 27 '20 at 17:25
  • @Alexander-ReinstateMonica They aren't enforced by cpython, but they are checked by mypy and used in other python runtimes like pypy to generate better code. This is not unusual: most typed languages don't do most of the type checking at runtime: they do it at compile time. In the case of python it just happens that the type-checking part of the compiler is a separate program, and some language implementations don't compile at all. – Phil Frost Jul 27 '20 at 17:27
  • In fact nullability context does not bring anything new to C#. Previously there was analysis to tell developer about using non-validated param on public API method. Internally you either have to count on `null` all the time or make sure that it is never passed to method. It makes at least the same meaning to get `NullReferenceException` or to get invalid data in case when one rather passes literally around some default non-null object. With exception that invalid data will come on light much later in production. One thing is really new. Much more agenda about null states. – Yarl Jul 03 '21 at 18:48
  • In fact `NullReferenceException` is in all cases blame of developer. Not runtime, not data etc. Dumbness of analysis results in many _overrides_ of null warnings. Same as before was `NullReferenceException` result of developer’s neglecting on verifying code paths and input parameters this time it will be result of nullable super-branded means chaos. – Yarl Jul 03 '21 at 18:56
  • PHP is another example of a language where null exists but types are not implicitly nullable. – bdsl Jul 03 '21 at 21:02
10

Great question, many developers wouldn't even think or care about! keep thinking and asking!

You are probably looking for optionals. Here, for example, in TypeScript that you use:

http://dotnetpattern.com/typescript-optional-parameters

The idea is to have explicitly state:

  • A) if an object can be null or not
  • B) unwrapping nullable objects before using them to avoid null pointer exceptions (NPEs)

The TypeScript parameter example I linked does not really do B)

if (address == undefined) {
  // Do something with a
} else {
  // Address not found, do something else
}

Maybe you can find a better way in TypeScript like it is done in Swift:

if let a = address {
  // Do something with a
} else {
  // Address not found. Do something else.
}

Or Kotlin:

address?.let { a ->
  // Do something with a
} ?: {
  // Address not found. Do something else
}()

The difference is that in the TypeScript example above you can still forget the check and just address, leading to an NPE. Optionals like in Swift or Kotlin will force you to first check the value existence before you can use them.

A common mistake done then is to force unwarp the value because you think the value always exists:

address!.doSomething() // In Swift

address!!.doSomething() // in Kotlin

If you see code like this, run! Usually a value is an optional for a reason, so just skipping over that safety-measure and claiming "there is a value, I am sure" leads you back to the old NPE-coding we try to get rid of.

A quick search looks like you are not really able to do that in TypeScript without voodoo:

Is it possible to unwrap an optional/nullable value in TypeScript?

Peter Mortensen
  • 1,050
  • 2
  • 12
  • 14
Nicolle
  • 109
  • 2
  • It's good to see how another language address this. Feels in Swift and Kotlin we have to deal with null types. In TS, we can do `function(o: A)` so null/undefined cannot be pass into this function. – Mengo Jul 26 '20 at 23:34
  • 1
    @Mengo I think you misread something, in `Swift` or `Kotlin`, `A` is guaranteed to be non-null (so long as `A` isn't a typealias for some type like `B?`, hiding the optionality like your like your `Value` example) – Alexander Jul 27 '20 at 01:00
  • 2
    In Swift, if an object reference isn’t nullable then it cannot be nil. Absolutely impossible. And the compiler doesn’t even allow you to check if it is nil. – gnasher729 Jul 27 '20 at 09:31
  • 1
    I think there are some legitimate use cases for force-unwrapping, sometimes you know more than the compiler. But generally this answer explains the concept well. – Mark Jul 27 '20 at 10:32
  • @gnasher729: Languages which don't allow one to check for things which are supposed to be impossible can make it hard to protect code against security exploits. That's fine if the languages will only be used in contexts where they will never receive maliciously-constructed data, but many languages need to be suitable for use outside such sheltered environments. – supercat Jul 27 '20 at 17:25
  • @supercat The system checks plenty of times. Every assignment of a pointer to a variable increases a reference count, and that operation will crash if the pointer is nil. – gnasher729 Nov 11 '20 at 00:47
  • @gnasher729: Will the assignment increase and then decrease the reference count in contexts where the compiler can see that the pointer is short-lived? If `x` is supposed to be a reference to a structure `x` containing an array of 150,000 bytes, code sets `p` to the address of `x`, uses it to write element 149,000 of the array, and then abandons `p`, would the generated code hit the reference count or anything else within the first 65,536 bytes of the array before writing to offset ~149,000 from the received address of `x`? – supercat Nov 11 '20 at 16:12
5

You should never have a non-initialised object (with the single exception of a short interval of time while you are initialising the object). Null specifies the absence of an object.

The problem is not null pointers. The problem is that in older languages

  1. You cannot specify that a pointer cannot be null,
  2. You cannot specify that a pointer can be null, and
  3. If a pointer is null nothing stops you from accessing what it points to, resulting in a crash or worse.

This results in case 1 in unnecessary code to check for null pointers and handling them, and no compiler warning if a null pointer is assigned, in case 2 in incorrect assumptions that a pointer would be non-null when it isn’t, and in case 3 it crashes.

Ian Boyd
  • 24,484
  • 1
  • 17
  • 16
gnasher729
  • 42,090
  • 4
  • 59
  • 119
  • I'd argue the *concept* of using pointers is in fact the root of the problem. Pointers should ideally be an implementation mechanism the compiler uses internally in some places for efficiency, not a first-class type that developers have to fumble with manually. In languages that have them, your code will be more reliable the more you can avoid them. – T.E.D. Jul 27 '20 at 14:02
  • @T.E.D I think the problem there is that it's really, really hard to avoid any leaking of that particular abstraction. Certainly in .NET but also TypeScript there are a large number of false positives and false negatives and fun edge cases all around. I really have to look around to see if there's a language that can really guarantee that a non-null reference can never be seen as null. – Voo Jul 27 '20 at 15:16
  • Voo you mean a non-nullable reference cannot be nil? I really, really hope that a non-null reference won’t ever be seen as null :-) in Swift you can ran into trouble if you interface with Objective-C Code returning null Even if declared non-nullable, not if you stay within Swift. – gnasher729 Jul 27 '20 at 15:40
  • @Voo - Well, classic Fortran had no pointers. Most coders used COMMON blocks to perform type conversions or to overlay storage at specific places. Linked structures generally had to be implemented in preallocated arrays using indices (which of course had a lot of the same problems as pointers). – T.E.D. Jul 27 '20 at 16:21
  • There are several more languages I've come across that stylistically actively discourage pointer use though. The first ones that come to mind are Ada and Concurrent C, but I suspect many (most?) functional languages are in this category as well. – T.E.D. Jul 27 '20 at 16:24
  • @gnasher729 If you have to make non-null work in an existing language there are simply edge cases that are impossible to avoid. .NET allows virtual calls in the constructor which alone is a death sentence for any 100% non-null guarantee. There are many other such edge cases. I'm not sure if all of these can be avoided if you were designing a language from ground up with non-nullability in mind. – Voo Jul 27 '20 at 20:18
  • @Voo Swift doesn't allow any calls in a constructor until all members are initialised. (Subclass initialises the subclass members, then calls super.init, and at that point everything is initialised and virtual functions are safe to use). – gnasher729 Nov 11 '20 at 00:51
  • @gnasher Very cool, that sounds like it should work. How does it deal with say arrays of non nullable references? I tried to search for it, but given that I've never written a line of swift I find it hard to find the right nomenclature. – Voo Nov 11 '20 at 08:57
  • An array of nullable references obviously must never contain a null reference :) You can initialise it by passing a number of non-null references like [x,y,z]. Or by copying an array of non-null references. You can’t just say “a is an array with 10 non-null references”. – gnasher729 Jul 04 '21 at 15:46
  • Note that optional doesn't solve the third problem, no matter how much optional zealots say it does. All optional techniques boil down to either "crash if null" or "silently do nothing if null" and the latter is not necessarily any better than the former. – user253751 Jul 05 '21 at 11:06
  • @user253751 but languages can make it easier or harder to ignore that case. The only languages where "silently do the wrong thing" is impossible in every case are languages that have at most one possible effect. – Caleth Nov 25 '21 at 12:35
4

There are many factors that contribute to null being a "Billion Dollar Mistake". Based on your comments, you're looking specifically at TypeScript and the type of possibly-uninitialized values, so I'll boil it down to two key problems: type safety, and value semantics.

The key - a.k.a., tl;dr

Especially in TypeScript, null-ish values are perfectly valid so long as you assign a simple, clear meaning to it ("no value"), and so long as the language supports this by only permitting null-ish values when the type explicitly allows it.

TypeScript naturally guards against accidental null values.

Others have covered type safety, but the key here is that, in TypeScript, you can include or exclude null and undefined as valid values of a type. At least in strict mode, number, number | null, and number | undefined are all separate types. Types are mathematical sets of valid values, and number only covers numbers. That means that (barring any deliberate breaking of the type system), you don't have to worry about a value unexpectedly being null or undefined. The compiler won't let you accidentally do that. In that regard, the type safety of null-like values is not such a big deal in TypeScript, which just leaves semantics.

So what do null and undefined mean?

In short, they mean "no value" - nothing more, nothing less. The number type can be any number, but it must be a number. In some cases, you want a type that's "a number, or nothing" - that's where you might use number | undefined. If you want something other than "has a value or doesn't" - such as "technically a value, but invalid" or "there was an error trying to compute the value", you need to use something other than null or undefined. The exact solution depends on the exact use case - over-generalizing is what caused the problem in the first place!

So why are there two?

This illustrates the "semantics" problem of the Billion Dollar Mistake quite well! Your colleague was right to point out that, in JavaScript, undefined means "was never defined", and shouldn't be allowed after initialization. That leaves null to mean... anything else that isn't a real value? However, TypeScript is not JavaScript! Believe it or not, the team who creates TypeScript (which is, itself, written in TypeScript) does not use null at all! These experts in the topics of language and especially of TypeScript and JavaScript have decided that null does not provide any value above and beyond undefined. But they do use undefined.

I don't want to misrepresent them, but I believe that this is because they're focusing on what those values mean above and beyond how the JavaScript engines use them. Now, undefined simply means "no value". They don't want to add any more than that, so they don't recognize a difference between that and null.

What does this mean for my example?

There's some missing information about what null is supposed to mean, but you seem to be doing the right thing. However, I would ditch the type Value = Node | null and also switch to undefined. Something like:

const [value, setValue] = React.useState<Node | undefined>()

function test(value: Node | undefined) {
  if (value != undefined) // TS knows `v` is a `Node`, not `undefined`
  else // TS knows `v` is `undefined`
}

Bonus: ? operator vs explicit undefined

You can use ? when declaring a value's identifier to suggest that it's optional. This automatically means it can be undefined (as that is the default for when a value is not provided), but it means more than that. function test(value: Node | undefined) means "test requires one argument, which may be undefined", whereas test(value?: Node) means "test can be called with 0 arguments, or with a Node, or with undefined". It's a small difference, but sometimes you want to say "you must tell me what it is, even if you tell me there's no value", in which case you should avoid ?.

Philipp
  • 23,166
  • 6
  • 61
  • 67
TheRubberDuck
  • 933
  • 6
  • 13
  • 1
    Interesting approach, but I see null meaning “no value” which is very much different from “undefined” or “unknown value”. – gnasher729 Jul 27 '20 at 15:46
  • 1
    This is only true when the strictNullChecks compiler option is set. Most typescript projects will allow null to be assigned to a number. – C.M. Jul 27 '20 at 15:57
  • @gnasher729 Yes, you can assign whatever meaning you want to it. I'm just appealing to the opinions of those who arguably best know the ins and outs of the language. Personally, I find the introduction of a secondary value to be clunky and unnecessary. Rarely do I need the difference between "uninitialized" and "initialized to no value" - but if you do, by all means use `null` for that. But, if it matters WHY the value is not defined, make that explicit with a better-named value. – TheRubberDuck Jul 27 '20 at 16:24
  • 2
    @C.M. I'm not sure about "most TypeScript projects" - they certainly recommend strict mode. In any case, I sort of called that out: "At least in strict mode, `number`, `number | null`, and `number | undefined` are all separate types." – TheRubberDuck Jul 27 '20 at 16:26
1

Just don't allow null values.

Require initialization when declaring a reference variable and do not allow to assign null to a reference later.

When a developer actually has a legitimate reason to assign a value which represents the absence of a value (rather the exception than the norm in real world software development), then there are two options.

  • They can assign a Null Object. A Null Object is a placeholder object which represents no value, but implements the full interface. So if anyone calls a method or accesses a field of the Null Object, the developer is obligated to implement what is supposed to happen in that case (a no-op, return a placeholder value, or throw a more specific exception).
  • They can use the Optional pattern. An Optional is a type which contains either an actual value or doesn't. The actual value can usually be accessed in various ways, but every way requires to state what's supposed to happen when no value is present (again: do nothing, return a placeholder or throw a specific exception). Programming such a class in such a language would of course be impossible without also implementing a null-object, which you are then not going to use. So you would either require Optionals to be a part of the language syntax (like nullable types in Kotlin) or add some additional syntax which allows you to create nullable types without creating null-objects (like the None type in Rust which can be used as one possible option of a heterogeneous type)
Philipp
  • 23,166
  • 6
  • 61
  • 67
  • Optionals can be and often are implemented as library types. No special syntax needed, although some may be used to improve ergonomics. If your language supports [tagged unions](https://en.wikipedia.org/wiki/Tagged_union), implementing optionals is outright trivial. – 8bittree Jul 27 '20 at 18:01
  • @8bittree How would you implement an optional in a programming language which does not allow to assign `null` to a reference? – Philipp Jul 27 '20 at 19:54
  • Well, if I were to implement it in Rust, I'd probably start with something like this: `Enum Option { None, Some(T), }`. That happens to be fairly similar to how it's actually implemented in the [core library](https://github.com/rust-lang/rust/blob/1.45.0/src/libcore/option.rs#L150). – 8bittree Jul 27 '20 at 20:21
  • @8bittree `None` is just `null` by another name. Rust is pretty smart by making null/None a separate type so there is not much you can do with None except using it in tagged unions. But it's still a null. – Philipp Jul 27 '20 at 21:09
  • Well, sort of. Conceptually, both `null` and `None` can indicate that there's no data. But `null` is special cased. `None` is just a generic variant with no associated value. And, going back to my original comment, there's no special syntax needed to implement this sort of optional. – 8bittree Jul 27 '20 at 21:35
1

It seems you are using TypeScript, is this correct? TypeScript has proper support for nulls in its type system (option strictNullChecks), so using null to indicate a missing or un-initialized value is perfectly fine. (Of course it it always best to avoid uninitialized values in the first place, but that is not always possible.)

The thing about the Million Dollar Mistake is really a misunderstanding. The mistake, as described by Hoare, was to have nulls be a possible value for any reference type. This is the case in Java and some other languages, but is not the case in TypeScript.

In TypeScript nulls are not a member of any object type but is rather a separate type of its own. So there really is no such thing as a "null reference" in TypeScript. More imporatantly, this means nulls are explicit in the type system, so you always know if a value can be null and you have to check for it.

sleske
  • 10,095
  • 3
  • 29
  • 44
JacquesB
  • 57,310
  • 21
  • 127
  • 176
0

Most of the dangers associated with null stem from the way common C implementations treat it. In languages where attempts to dereference a null pointer are routinely trapped, a null pointer/reference may cause a program to rudely terminate as a consequence of trying to process uninitialized data, but such termination would have been the norm in strongly-trapped languages with or without null. The problem is that the way C handles pointer arithmetic makes it expensive to ensure that code won't inadvertently take a null pointer, apply an offset to it, and end up with a pointer which is meaningless but is no longer recognizable as a null or invalud pointer. Attempting to access such a pointer might shut down a program, but it may also corrupt data without shutting down the program, thus allowing the corrupt data to replace what would have been good data on disk.

supercat
  • 8,335
  • 22
  • 28
  • 1
    * [invalid](https://en.wiktionary.org/wiki/invalid#Adjective) – Peter Mortensen Jul 27 '20 at 15:44
  • @PeterMortensen: Do you disagree that in C code inappropriate attempts to use null pointers are likely to result in undetected data corruption, while many languages other than C could trap 100% of attempts to dereference null pointers without any such risk? – supercat Jul 27 '20 at 17:17
  • @PeterMortensen: Although the invention of null pointers predates the invention of the C language, I'm unaware of any evidence that he viewed them as a "billion dollar mistake" until long after C had become popular, and many C programs had malfunctioned in rather "interesting" ways because of how that language processes pointer arithmetic. [Incidentally, the costs of safe trapping of null pointers in C could have been greatly reduced if hardware vendors had included a variation of "lea" which would trap when using a zero base and non-zero offset]. – supercat Jul 27 '20 at 17:22
  • 2
    Yes, in C, using bad pointers (be they dangling, wild, null, misaligned, not pointing to the right type or amount of data, ...) can have all kinds of interesting effects. Singling out just null as the root of all evil is a bit much though. Actually, saying C does anything wrong there just means someone chose the wrong language for their project. – Deduplicator Jul 27 '20 at 17:34
  • @Deduplicator: The problem with C, compared with other languages, is the use of pointers to simultaneously encapsulate arrays and offsets therein. In a language like Pascal, if one wants a function to operate on a range of an array, one would need to pass a pointer to the array, along with integers identifying the starting element and either the count, last element, or just-beyond element. Code using such a triple would then index the unmodified pointer, allowing the compiler to null-check it. In C, it would be much more common to have the caller... – supercat Jul 27 '20 at 17:38
  • ... add the starting offset to the array's address and pass the resulting address, along with a count. Use of pointers into arrays can reduce the amount of address calculation compilers have to do at run time, but adding null checks to pointer arithmetic operations as well as pointer-dereferencing operations would totally negate any performance advantage that would have been gained by the simplification of indexing allowed by pointer arithmetic. – supercat Jul 27 '20 at 17:41
  • @Deduplicator: In most languages, the cost of automatic null checks and array-bounds checks isn't trivial, but is often worth paying, and many implementations of those languages can be configured to provide them. C can offer better performance than many other languages in cases where such checks are not required, but could not offer such checks without forfeiting its performance advantages. – supercat Jul 27 '20 at 17:57
0

Dealing with temporary nullity during initialization is not yet a solved problem, but there are solutions. Kotlin has a few options built in to the language. One is to declare a member variable lateinit. This allows one to run one's initialization process with more flexibility. Under the hood, the variable is actually null at first, but later, null checks are not necessary.

Another technique is to delegate a member property (either variable or constant) to a Lazy object, which is accessed when the member property is accessed. Lazy wraps a value, and when accessed, blocks until it's available. The Lazy class is in the standard library, but the by keyword (which declares the delegation relationship) is language-level, and allows for you to use a class other than Lazy, so this option might be good for cases where you need a lot of flexibility in your initialization process.

Although these answers are Kotlin-specific, they are patterns that can be used in other object oriented languages.

Travis Well
  • 101
  • 2
-1

Initialization was not delayed since you initialized variable with null.

In JavaScript one can leave variable in undefined state. (Maybe your co-worker likes it more than null state.)

Then go you for

if(value)
  …
else
 … 

That perfectly evaluates for null and undefined.

If something is mistake, it is usually shortly removed. With exception for trivial cases like HTTP referer header field name.

For no-null languages see answer for Are there languages without “null”?. Choose some and educate yourself.

One can truly never avoid no-value state. When data comes from network you cannot tell “oh its int = 0, so it’s nothing” since it is azimuth.

For instance C# nullable annotation context solves in fact nothing. It’s just annotation that brings in not little amount of agenda. To warrant analysis originating from it is valid is at least of same difficulty as it was before to warrant that dereferenced variable is not null all the time.

That is since it is just annotation. It guarantees no thing.

See Known pitfalls for further reference on another “salvation failure” of nullable reference types.


“Null References: The Billion Dollar Mistake“ mean technically nothing. You can for instance check nuclear plant or even space shuttle accidents. Even the most verified technical ways can fail. Nobody doubt that money in terms of remittance means not much in such cases.


Usually world is not blackwhite stream. Optimistically said nullable annotation machinery has potential to solve things at cost of heaviness

Apparently, if null does not exists in language, there can be no problems about invokes on it etc.

Yarl
  • 288
  • 2
  • 13
  • 1
    Swift allowed “if nullableobject” as a nil test. Then someone figured out that you can have an optional (nullable) bool which can have values nil, true and false. So “if b” would execute if b was a nullable Bool with value false, but not if it was a normal not-nullable bool with value false. That was deemed too dangerous. Now you can use “if b = true” (not nil, not false) or “if b = false” (not nil, not true) or “if b != nil” (not nil, only available for nullable bool). – gnasher729 Jul 04 '21 at 15:53
  • @gnasher729, Swift involutes are passing me right outwith. Making `value type` nullable is need. Making `reference type` non-nullable is trend. Which part of my post is this related to? – Yarl Jul 04 '21 at 18:37
-3

Nullability is not the billion-dollar mistake. Useless programmers who don't validate inputs, are.

Languages or programs that attempt to "solve" the nullability "problem" by introducing monads or optionals or whatever they want to call them, are merely obfuscating the issue of lacking input validation behind an extra layer of abstraction. Essentially, they're trying to do useless programmers' jobs for them.

But that doesn't actually help anything at the end of the day, because a program that doesn't validate its arguments is going to misbehave regardless of whether it receives null or MonadOrOptionalOrMaybeOrWhateverNameYourLanguageThinksIsCool<MyClass>.Default. At least with nullability, you'll get a crash or at the very least a distinctive stack trace; with a monad, your app just won't do what you were expecting it to. Which one of these cases is easier to diagnose and fix?

Ensuring your program runs correctly is your responsibility as the programmer, not the language's. Verify your damn inputs.

Ian Kemp
  • 379
  • 1
  • 11
  • [Douglas Crockford wants to replace "null" with an immutable empty object](https://www.youtube.com/watch?v=MBWAP_8zxaM&t=12m48s). – Peter Mortensen Jul 27 '20 at 16:00
  • 7
    Calling people names does not make a good answer. Aside from that, I'm seeing a misunderstanding about "MonadOrOptionalOrMaybeOr...". For one, "monad" is a general term that refers to more than the optional/maybe pattern. But it does more than push the issue aside, but brings it to the forefront. If something is passed in as a "maybe" type, you know that you still have to "verify your damn inputs", whereas if it's passed around as the underlying type, that verification has clearly already been done. – TheRubberDuck Jul 27 '20 at 17:35
  • 3
    In my experience, the program with the Optional is vastly easier to diagnose and fix, because the compiler brings the problem to my attention before the program ever runs. No need to hope and pray that I'll get just the wrong test data to trigger that NPE before it makes it out to production, freeing me up to think about more important things, like what to do in the case of not having data, or what to do with the data when I do have it. – 8bittree Jul 27 '20 at 18:17