Dart Null Safety: The Ultimate Guide to Non-Nullable Types
Tags: dart
Published: 2020-06-29 10:21:06

---
title: Dart Null Safety: The Ultimate Guide to Non-Nullable Types
published: true
description: A complete tour of Null Safety & non-nullable types, the syntax changes they introduce in Dart 2.9, and how to use them in practice.
tags: dart
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/9j2d5y2qmspokmvhkma4.png
canonical_url: https://codewithandrea.com/videos/dart-null-safety-ultimate-guide-non-nullable-types/
---

The introduction of Null Safety in Dart 2.9 marks a major milestone for the language. Null Safety helps you avoid an entire class of problems, and enables some performance improvements.

This article outlines what's changed, and shows how to use the new Null Safety features by example.

_As of June 2020, Null Safety is available in Tech Preview on the Dart [dev channel](https://dart.dev/get-dart#release-channels). It's not yet available in the Flutter SDK, but you can try it at [nullsafety.dartpad.dev](https://nullsafety.dartpad.dev/):_


![Dartpad with Null Safety](https://dev-to-uploads.s3.amazonaws.com/i/gwv8m7serv3cx30l8fbd.png)

This contains a "Learn from Snippets!" dropdown with mini-exercises to get familiar with the new syntax.

## Table of Contents

- [Some context](#some-context)
- [Dart Type System](#dart-type-system)
- [Dart Null Safety: Benefits](#dart-null-safety-benefits)
- [Declaring Non-Nullable Variables](#declaring-non-nullable-variables)
- [Declaring Nullable Variables](#declaring-nullable-variables)
- [The assertion operator](#the-assertion-operator)
- [Flow Analysis: Promotion](#flow-analysis-promotion)
- [Flow Analysis: Definite Assignment](#flow-analysis-definite-assignment)
- [Using non-nullable variables with classes](#using-non-nullable-variables-with-classes)
- [Non-nullable named and positional arguments](#non-nullable-named-and-positional-arguments)
- [Null-aware cascade operator](#null-aware-cascade-operator)
- [Null-aware subscript operator](#null-aware-subscript-operator)
- [The late keyword](#the-late-keyword)
- [Static and global variables](#static-and-global-variables)
- [Conclusion](#conclusion)
- [References](#references)

## Some context

Null References were [first introduced](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/) in 1965 in the ALGOL programming language, and since then they have been adopted by most mainstream programming languages.

However, null errors are so common that null references have been called the The Billion Dollar Mistake.

![Null References Mistake](https://dev-to-uploads.s3.amazonaws.com/i/tpp31119moxe2t8uuz17.jpg)

So let's see what's changed in Dart to address this.

## Dart Type System

Before addressing Null Safety, let's talk about the Dart type system.

Dart is said to have a **sound type system**. When we write Dart code, the **type checker** makes sure that we can't write something like this:

```dart
int age = "hello world"; // A value of type `String` can't be assigned to a variable of type `int`
```

This code produces an error telling us that _"a `String` value can't be assigned to a variable of type `int`"_.

Similarly, when we write a function in Dart, we can specify a return **type**:

```dart
int square(int value) {
  return value * value;
}
```

Because of **type safety**, Dart can guarantee with 100% confidence that this function **always** returns an `int`.

> Type safety help us write safer programs, and more easily reason about the code.

But type safety alone can't guarantee that a variable (or return value) is not `null`.

As a result this code compiles, but generates an exception **at runtime**:

```dart
square(null);
// Unhandled Exception: NoSuchMethodError: The method '*' was called on null.
```

In this example it's easy enough to spot the problem. But in large codebases it's hard to keep track of what can and cannot be `null`.

Runtime `null` checks can mitigate the problem, but they add more noise:

```dart
int square(int value) {
  assert(value != null); // for debugging
  if (value == null) throw Exception();
  return value * value;
}
```

What we really want here is to tell Dart that the `value` argument should **never** be `null`.

A better solution is needed - and now we have it. 😎

## Dart Null Safety: Benefits

Dart 2.9 introduces Sound Null Safety as a language feature, and brings three main benefits:

- We can write null-safe code with strong **compile-time** guarantees. This makes us productive because Dart can tell us when we're doing something wrong.
- We can more easily declare our **intent**. This leads to APIs that are self-documenting and easier to use.
- The Dart compiler can optimise our code, resulting in smaller and faster programs.

So let's see how Null Safety works in practice.

## Declaring Non-Nullable Variables

The main language change is that all types are now non-nullable **by default**.

This means that this code doesn't compile:

```dart
void main() {
  int age; // non-nullable
  age = null; // A value of type `Null` can't be assigned to a variable of type 'int'
}
```

When using non-nullable variables, we must follow one important rule:

> Non-nullable variables must always be initialized with non-null values.

If you reason along these lines, it will be easier to understand all the new syntax changes.

----

Let's revisit this example:

```dart
int square(int value) {
  return value * value;
}
```

Here both the `value` argument and the return value are now **guaranteed** to be not `null`.

As a result **runtime** `null` checks are no longer necessary, and this code now produces a **compile-time** error:

```dart
square(null);
// The argument type 'Null' can't be assigned to the parameter type 'int'
```

But if all types are now **non-nullable** by default, how can we declare **nullable** variables?

## Declaring Nullable Variables

The `?` symbol is what we need:

```dart
String? name;  // initialized to null by default
int? age = 36;  // initialized to non-null
age = null; // can be re-assigned to null
```

> Note: You don't need to initialize a nullable variable before using it. It is initialized to `null` by default.

Here are some other ways of declaring nullable variables:

```dart 
// nullable function argument
void openSocket(int? port) {
  // port can be null
}

// nullable return type
String? lastName(String fullName) {
  final components = fullName.split(' ');
  return components.length > 1 ? components.last : null;
}

// using generics
T? firstNonNull<T>(List<T?> items) {
  // returns first non null element in list if any
  return items.firstWhere((item) => item != null);
}
```

> Take away: you can declare nullable variables **anywhere** in your code with the `?` syntax.

Nullable variables are a good way of expressing the **absence** of a value, and this is useful in many APIs.

When you design an API, ask yourself if a variable should be nullable or not, and declare it accordingly.

But there are cases where we know that something can't be `null`, but we can't **prove** it to the compiler. In these cases, the assertion operator can help.

## The assertion operator

We can use the assertion operator `!` to assign a nullable expression to a non-nullable variable:

```dart
int? maybeValue = 42;
int value = maybeValue!; // valid, value is non-nullable
```

By doing this, we're **telling** Dart that `maybeValue` is not `null`, and it's safe to assign it to a non-nullable variable.

Note that applying the assertion operator to a `null` value will throw a runtime exception:

```dart
String? name;
print(name!); // NoSuchMethodError: '<Unexpected Null Value>'
print(null!); // NoSuchMethodError: '<Unexpected Null Value>'
```

> When your assumptions are wrong, the `!` operator leads to runtime exceptions. 

---

Sometimes we need to work with APIs that return nullable values. Let's revisit the `lastName` function:

```dart
String? lastName(String fullName) {
  final components = fullName.split(' ');
  return components.length > 1 ? components.last : null;
}
```

Here the type system can't help. If we **know** that the function **will** return a non-`null` value for a given argument, we should assign it to a non-nullable variable **as soon as possible**.

This is done with the `!` operator:

```dart
// prefer this:
String last = lastName('Andrea Bizzotto')!;
// to this:
String? last = lastName('Andrea Bizzotto');
```

In summary:

- Try to create non-nullable variables when possible, as these will be **guaranteed** to be not `null` at **compile time**.
- If you know that a nullable expression won't be `null`, you can assign it to a non-nullable variable with the `!` operator.

## Flow Analysis: Promotion

Dart can make your life easier by taking into account `null` checks on nullable variables:

```dart
int absoluteValue(int? value) {
  if (value == null) {
    return 0;
  }
  // if we reach this point, value is non-null
  return value.abs();
}
```

Here we use an `if` statement to return early if the `value` argument is `null`. 

Beyond that point, `value` cannot be `null` and is treated (or **promoted**) to a non-nullable value. Hence we can safely use `value.abs()` rather than `value?.abs()` (with the null-aware operator).

Similarly, we could throw an exception if the value is `null`:

```dart
int absoluteValue(int? value) {
  if (value == null) {
    throw Exception();
  }
  // if we reach this point, value is non-null
  return value.abs();
}
```

Once again, `value` is promoted to a non-nullable value, and the null-aware operator `?.` is not needed.

In summary:

- Use **upfront** null checks to return early or throw exceptions
- After null checks, nullable variables are **promoted** to be non-nullable

And after a nullable variable has been null checked, Dart lets you use it as a non-nullable variable, which is quite nice.



## Flow Analysis: Definite Assignment

Dart knows where variables are **assigned** and where they're **read**.

This example shows how to initialize a non-nullable variable **after** checking for a condition:

```dart
int sign(int x) {
  int result; // non-nullable
  print(result.abs()); // invalid: 'result' must be assigned before it can be used
  if (x >= 0) {
    result = 1;
  } else {
    result = -1;
  }
  print(result.abs()); // ok now
  return result;
}
```

As long as a non-nullable variable is given a value **before** it's used, Dart is happy.

## Using non-nullable variables with classes

Instance variables in classes must be initialized if they are non-nullable:

```dart
class BaseUrl {
  String hostName; // Non-nullable instance field 'hostName' must be initialized

  int port = 80; // ok
}
```

If a non-nullable instance variable can't be initialized with a default value, set it with a constructor:

```dart
class BaseUrl {
  BaseUrl(this.hostName);
  String hostName; // now valid

  int port = 80; // ok
}
```

## Non-nullable named and positional arguments

With Null Safety, non-nullable **named** arguments must always be **required** or have a **default value**.

This applies to regular methods as well as class constructors:

```dart
void printAbs({int value}) {  // 'value' can't have a value of null because of its type, and no non-null default value is provided
  print(value.abs());
}

class Host {
  Host({this.hostName}); // 'hostName' can't have a value of null because of its type, and no non-null default value is provided
  final String hostName;
}
```

We can fix the code above with the new `required` **modifier**, which replaces the old `@required` **annotation**:

```dart
void printAbs({required int value}) {
  print(value.abs());
}

class Host {
  Host({required this.hostName});
  final String hostName;
}
```

And when we use the above APIs, Dart can tell us if we're doing something wrong:

```dart
printAbs(); // The named parameter 'value' is required, but there's no corresponding argument
printAbs(value: null); // The argument type 'Null' can't be assigned to the parameter type 'int'
printAbs(value: -5); // ok

final host1 = Host(); // The named parameter 'hostName' is required, but there's no corresponding argument
final host2 = Host(hostName: null); // The argument type 'Null' can't be assigned to the parameter type 'String'
final host3 = Host(hostName: "example.com"); // ok
```

On the flip side, if we use **nullable** instance variables we can omit the `required` modifier (or the default value):

```dart
class Host {
  Host({this.hostName});
  final String? hostName; // nullable, initialized to `null` by default
}
// all valid cases
final host1 = Host(); // hostName is null
final host2 = Host(hostName: null); // hostName is null
final host3 = Host(hostName: "example.com"); // hostName is non-null
```

**Positional** parameters are subject to the same rules:

```dart
class Host {
  Host(this.hostName); // ok
  final String hostName;
}

class Host {
  Host([this.hostName]); // The parameter 'hostName' can't have a value of 'null' because of its type, and no non-null default value is provided
  final String hostName;
}

class Host {
  Host([this.hostName = "www.codewithandrea.com"]); // ok
  final String hostName;
}

class Host {
  Host([this.hostName]); // ok
  final String? hostName;
}
```

----

*Between nullable and non-nullable variables, named and positional arguments, required and default values, there's a lot to take in. If you're confused, remember the golden rule:*

> Non-nullable variables must always be initialized with non-null values.

*To fully understand all the Null Safety features, practice using them with Dartpad. Dart will tell if you're doing something wrong - so read the error messages carefully.* 🔍

## Null-aware cascade operator

To deal with Null Safety, the cascade operator now gains a new `null`-aware variant: `?..`. Example:

```dart
Path? path;
// will not do anything if path is null
path
  ?..moveTo(0, 0)
  ..lineTo(0, 2)
  ..lineTo(2, 2)
  ..lineTo(2, 0)
  ..lineTo(0, 0);
```

The cascade operations above will only be executed if `path` is not `null`. 

The null-aware cascade operator can **short-circuit**, so only one `?..` operator is needed at the beginning of the sequence.

## Null-aware subscript operator

Up until now, checking if a collection was `null` before using the subscript operator was verbose:

```dart
int? first(List<int>? items) {
  return items != null ? items[0] : null; // null check to prevent runtime null errors
}
```

Dart 2.9 introduces the `null` aware operator `?[]`, which makes this a lot easier:

```dart
int? first(List<int>? items) {
  return items?[0]; 
}
```

## The late keyword

Use the `late` keyword to initialize a variable when it is **first read**, rather than when it's **created**.

A good example is when initializing variables in `initState()`:

```dart
class ExampleState extends State {
  late final TextEditingController textEditingController;

  @override
  void initState() {
    super.initState();
    textEditingController = TextEditingController();
  }
}
```

Even better, `initState()` can be removed altogether:

```dart
class ExampleState extends State {
  // late - will be initialized when first used (in the build method)
  late final textEditingController = TextEditingController();
}
```

It's common to use `late` in combination with `final`, to **defer** the creation of **read-only** variables to when they are first read.

This is ideal when creating variables whose initializer does some heavy work:

```dart
late final taskResult = doHeavyComputation();
```

When used within a function body, `late` and `final` can be used like this:

```dart
void foo() {
  late final int x;

  x = 5; // ok
  x = 6; // The late final local variable is already definitely initialized
}
```

Though I don't recomment using late variables this way. Because this style can result in non-obvious runtime errors. Example:

```dart
class X {
  late final int x;
  
  void set1() => x = 1;
  
  void set2() => x = 2;
}

void main() {
  X x = X();
  x.set1();
  print(x.x);
  x.set2(); // LateInitializationError: Field 'x' has already been initialized.
  print(x.x);
}
```

By declaring a non-nullable `late` variable, we **promise** that it will be non-null at runtime, and Dart helps us with some compile-time guarantees.

But I recommend to only use `late` sparingly, and to always initialize `late` variables when they are declared.

## Static and global variables

All global variables **must now be initialized when they are declared** unless they are `late`:

```dart
int global1 = 42; // ok

int global2; // The non-nullable variable 'global2' must be initialized

late int global3; // ok
```

The same applies to static class variables:

```dart
class Constants {
  static int x = 10; // ok
  static int y; // The non-nullable variable 'y' must be initialized
  static late int z; // ok
}
```

But as I said before, I do not recommend using `late` this way as it can lead to runtime errors.

<!--
## Note about implicit downcasts

With Dart 2.9, **implicit downcasts** are no longer allowed. For a detailed explanation about this, see [this article](/articles/2020-06-22-implicit-downcast-no-longer-allowed/).
-->

## Conclusion

Null Safety is a major change for the Dart language, and it has been introduced to help you write better and safer code.

But at the end of the day, Null Safety is just a tool, and it is your job to use it correctly.

Every time you declare a variable in Dart, think about whether it should be nullable or not. This may seem like extra work, but it will lead to better code and Dart can help you along the way.

*As of June 2020, Null Safety is in Tech Preview, and is not intended to be used in production code. Full Null Safety as a stable feature is planned before the end of the year (for the latest updates see [dart.dev/null-safety](https://dart.dev/null-safety)).*

<!-- see the [full announcement](https://medium.com/dartlang/announcing-sound-null-safety-defd2216a6f3) -->

If you can't wait, you can port existing projects to Null Safety and automate the process with this [migration tool](https://pub.dev/packages/nnbd_migration).

## References

This article was mainly inspired by these sources:

- [Announcing sound null safety](https://medium.com/dartlang/announcing-sound-null-safety-defd2216a6f3)
- [What is Null Safety in Dart? | Stack Overflow](https://stackoverflow.com/questions/60068435/what-is-null-safety-in-dart/60068436#60068436)
- [Dart nullability syntax decision: a?\[b\] or a?.\[b\]](https://medium.com/dartlang/dart-nullability-syntax-decision-a-b-or-a-b-d827259e34a3)
- [Sound Null Safety | dart.dev](https://dart.dev/null-safety)

Migration resources: 

- [Null Safety Migration Tooling | pub.dev](https://pub.dev/packages/nnbd_migration)
- [How to migrate Dart code to Non-nullable (NNBD)? | Stack Overflow](https://stackoverflow.com/questions/62148950/how-to-migrate-dart-code-to-non-nullable-nnbd)

Happy coding!