Sound null safety

Dart · Essentials · 12 min

Null safety removes a whole class of crashes, and every Dart and Flutter API is written with it.

In Dart 3, types are non-nullable by default: a String can never be null. Add ? to allow it: String? nickname;. Nullable variables start as null; non-nullable locals must be assigned before use.

Null-aware operators:

  • a ?? b — a unless it is null, then b.
  • x ??= value — assign only if x is null.
  • user?.name — null if user is null, otherwise the property.
  • value! — assert non-null; throws if it is null. Use sparingly.

Flow analysis promotes types: after if (name != null), name is treated as String inside the block. The same happens after an early return or throw. Promotion only works for local variables and private final fields, because a public field or getter could return something different the next time.

late declares a non-nullable variable that is initialized later: late final String token;. Reading it before assignment throws. late with an initializer makes it lazy: the expression runs on first access.

Since Dart 3.8, collection literals support null-aware elements: [?maybeItem] adds the item only if it is non-null.

The guarantee is sound: once your program and its dependencies are null-safe, a non-nullable expression can never evaluate to null at run time.

Example

String greet(String? name) {
  if (name == null) return 'Hello, guest';
  return 'Hello, ${name.toUpperCase()}';   // promoted to String
}

class Config {
  late final String apiKey;              // set later, once
  late final int heavy = compute();      // lazy: runs on first read
  int compute() { print('computing'); return 42; }
}

void main() {
  print(greet(null));                    // Hello, guest
  print(greet('ana'));                   // Hello, ANA

  String? city;
  print(city?.length ?? 0);              // 0
  city ??= 'Oslo';
  print(city.length);                    // 4 (promoted after ??=)

  final c = Config()..apiKey = 'k-1';
  print(c.heavy);                        // computing / 42
  String? tag;
  print([1, ?tag].length);               // 1 (null-aware element)
}

Remember

  • Types are non-nullable unless marked ?
  • Prefer ??, ?. and null checks over !
  • Flow analysis promotes local variables after a null check

Watch out for

  • Expecting a class field to promote after if (field != null) — copy it to a local first.

Check yourself

  1. Why doesn't `if (this.name != null) print(this.name.length);` compile when `name` is a public non-final `String?` field?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: dart.dev/null-safety/understanding-null-safety

Sound null safety · Dart · Topolearn