Dart null safety - Solving the billion-dollar mistake
Tags: dart, beginners, flutter, tutorial
Published: 2020-11-23 18:29:37

In a 2009 talk, **Tony Hoare** traced the invention of the null pointer to his design of the Algol W language and called it a "mistake":

> 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 (ALGOL W). 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.

<small>Source: https://en.wikipedia.org/wiki/Void_safety</small>

# What is null safety?

Null was devised as a special value to represent the **intentional** absence of any value. This is different from an `empty string`, a `zero int` or `false`. It may or may not exist. 

```dart
Color favoriteColor;
```

`favoriteColor` can be either a `Color` object or `null`. This distinction is important understand. Developers may incorrectly refer to the null value as:
> Color object which has a null value

On an average, 80 - 90% of variables in our code will have a value. An `int` will be initialized to a number or is set before being used. This is valid and no harm will fall upon your app. But as we all know, this happens more than 10% of the time:

![Null pointer everywhere](https://dev-to-uploads.s3.amazonaws.com/i/9dcz7ljlcgm4y24h79gp.jpeg)

To be clear, null-safety does not mean nulls are bad. On the contrary, a null value has valid use cases. For example,

```dart
class Person {
  String firstName;
  String middleName;
  String lastName;
}
```
In the above case, an application can assume `firstName` and `lastName` will be present and may perform no checks. `middleName` may not be present, in which case, it will either be a `String` or `null`.

Null safety is crucial to avoid human errors. Null safety provides a declarative mechanism to understand application logic.

Let's contrast a code block with and without null safety. 

Without null safety, a typical code block would look like:

```dart
void middleAllCaps(Person p) {
  return p.middleName.toUpperCase();
}
```

It would work in most cases, and when `middleName` is null we would encounter the dreaded Null error:

```
Uncaught TypeError: Cannot read property 'toUpperCase$0' of nullError: 
TypeError: Cannot read property 'toUpperCase$0' of null
```

Show the above to a developer, and you would get a staunch reply, "No worries! I got this", and a null check would appear.

```dart
void middleAllCaps(Person p) {
  if (p.middleName == null) return '';
  return p.middleName.toUpperCase();
}
```

![NPE](https://dev-to-uploads.s3.amazonaws.com/i/hoq5jeutggf4mdu3p8k3.jpeg)

Again, this is a good answer but as developers we forget to do this before going to production, and that is a problem.
Why not let the language help us deal with **potential** null variables. 

With null safety on, and we'll talk about how to enable it for dart, we have options of indicating "nullability".

## Non nullable

By default, with null safety on, all variables are non nullable. This will be valid for 80-90% of your code.

```dart

class Person {
  String middleName;
}

void middleAllCaps(Person p) {
  return p.middleName.toUpperCase();
}
```

The code above will not compile because we have indicated `middleName` as non-nullable but not initialized it. 

```
Error: Field 'middleName' should be initialized because its type 'String' doesn't allow null.
  String middleName;
           ^^^^^^^^^^
Error: Compilation failed.
```

One solution is to add the constructor with required parameter. The other is `late` keyword which we will talk about later.

```dart
class Person {
    String middleName;
    Person(this.middleName);
}

String middleAllCaps(Person p) {
    return p.middleName.toUpperCase();
}
```

In essence, Dart will perform flow analysis to ensure a non-nullable variable is initialized.

## Nullable or the ?

You can indicate to Dart that a variable is nullable i.e. it can have a value or not. Nullable variables do have the potential for blowing up. Dart will use flow analysis to prove that no such error can occur. If it cannot it will ask the developer to handle it during compilation itself.


```dart
class Person {
  String? middleName;
}

void middleAllCaps(Person p) {
  return p.middleName.toUpperCase();
}
```

This would cause a compilation error. 

```
Error: Method 'toUpperCase' cannot be called on 'String?' because it is potentially null.
  return p.middleName.toUpperCase();
                        ^^^^^^^^^^^
Error: Compilation failed.
```

Why? The `?` indicates that the variable can have a null value. Dart's flow analysis will identify a potential null error and halt compilation. The developer is expected to explicitly handle the condition such that `p.middleName` is not accessed or read when the value is null. Or in other words, add a null check.

```dart
void middleAllCaps(Person p) {
  return p.middleName?.toUpperCase() ?? '';
}
```

So far so good! Dart's flow analysis will detect the null check and continue compilation.

## Nullable force override or the !

Dart's flow analysis will not detect setters especially where classes are involved. What if you have a class with no constructor but you ensure initialization outside of it. Dart may not understand that flow in which case you want to force Dart to assume the value will always be available. You do that by adding the `!` to the accessor. 

```dart
class Person {
  String? middleName;
}

String middleAllCaps(Person p) {
  // The trailing ! symbol will skip null check
  return p.middleName!.toUpperCase();
}

void main() {
  Person p = Person();
  p.middleName = 'Fancy';
  print(middleAllCaps(p));
}
```

The developer is responsible for setting the value before accessing it. There may be a handful of cases where overriding null check is acceptable but generally  considered a code smell.

## required keyword

When creating classes using positional non-nullable fields, Dart's flow analysis may not detect setting of variables. E.g.

```dart
class Person {
  String middleName;
  Person({this.middleName});
}
```
This will yield a compilation error. `middleName` is non-nullable but the constructor will set it to null if value is not passed. This is not acceptable. You can use the `required` keyword to indicate that `middleName` is a required non-nullable positional parameter.

```dart
class Person {
  String middleName;
  Person({required this.middleName});
}
```

## late keyword

Not all non-nullable fields may be initialized in the constructor. The analyzer cannot prove that the value will be set before access. Such as the following case:

```dart
class Person {
  String middleName;

  void assignRandomMiddleName() {
    middleName = 'Wilcox';
  }
}

String middleAllCaps(Person p) {
  return p.middleName.toUpperCase();
}

void main() {
  Person p = Person();
  p.assignRandomMiddleName();
  print(middleAllCaps(p));
}
```

In such cases, you should not force override using `!` but rather tell Dart that the variable will be initialized before reading the value like so:

```dart
class Person {
  late String middleName;
}
```

If you set the `late` keyword and do not initialize it, you won't get a nullError but rather a `LateInitializationError`:
```
Uncaught Error: LateInitializationError: Field 'middleName' has not been initialized.
```

# How to enable null safety

Null safety is still experimental. Library developers have already started pushing out null safe versions but you can continue run your apps without switching over.To opt-in to null safety for your project make sure:

* You have dart version 2.10+
* All dependencies are null safe

To run an app using null safety on:

```shell
dart --enable-experiment=non-nullable --no-sound-null-safety bin/myapp.dart
```

To enable your IDE to provide null safety errors you have to modify your `analysis_options.yaml` and add the following options:

```yaml
# Defines a default set of lint rules enforced for
# projects at Google. For details and rationale,
# see https://github.com/dart-lang/pedantic#enabled-lints.
include: package:pedantic/analysis_options.yaml

# For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
# Uncomment to specify additional rules.
# linter:
#   rules:
#     - camel_case_types

analyzer:
  enable-experiment:
      - non-nullable
```
# How do I make my code null safe

dart comes with a migration tool built-in. More options are available on migration in the dart docs

```shell
dart migrate
```