Question #333MediumDart BasicsImportant

What is sound null safety?

#dart#null-safety#type-system

Answer

Overview

Sound null safety is Dart's guarantee that non-nullable types can NEVER contain null -- enforced at compile time, not just runtime. It was introduced in Dart 2.12 (stable) and is enabled by default in all modern Flutter/Dart projects.


What 'Sound' Means

Without null safety (before Dart 2.12):

dart
// Old Dart -- any variable could be null, no compile-time check
String name = null;       // Allowed -- crashed at runtime
name.length;              // NullPointerException at RUNTIME

With SOUND null safety:

dart
// Modern Dart -- null safety is SOUND (complete, no exceptions)
String name = null;     // COMPILE ERROR -- caught before running!
String? name = null;    // OK -- explicitly declared nullable

// Compiler PROVES name is non-null here -- no runtime check needed
if (name != null) {
  print(name.length);   // Safe -- compiler promoted to String (non-null)
}

Non-nullable vs Nullable

dart
// Non-nullable (default) -- guaranteed to never be null
String name = 'Alice';     // Never null
int age = 28;              // Never null
List<User> users = [];     // Never null

// Nullable -- can hold null (add ?)
String? email = null;      // Can be null
int? score = null;         // Can be null
User? currentUser = null;  // Can be null

Working with Nullable Types

dart
String? getEmail(int userId) {
  // May return null
  return userId == 1 ? 'alice@example.com' : null;
}

void process() {
  final email = getEmail(1);

  // Option 1: null check (compiler promotion)
  if (email != null) {
    print(email.length); // No ! needed -- compiler knows it's String here
  }

  // Option 2: null coalescing
  final display = email ?? 'No email';

  // Option 3: null-aware access
  print(email?.toUpperCase()); // null if email is null

  // Option 4: assert non-null (dangerous -- throws if null)
  print(email!.length); // Throws if null
}

late -- Deferred Non-nullable Initialization

dart
class UserProfile {
  late String name;       // Non-nullable but initialized later
  late final String id;   // Non-nullable final -- set once, later

  void init(String userId) {
    name = 'Alice';   // Must be set before use
    id = userId;      // Can only set once (final)
  }
}

// If accessed before init -- LateInitializationError (runtime)

Why 'Sound' Matters

text
Unsound null safety -- can have runtime null errors despite type annotations
Sound null safety -- IMPOSSIBLE to get a null error on a non-nullable type

Dart's is SOUND because:
  1. All code (including packages) must be null-safe
  2. No hidden nullable backdoors
  3. Compiler proves null safety at every point

Benefits

BenefitDescription
No NullPointerExceptionNon-nullable types guaranteed safe
Better optimizationCompiler skips null checks in generated code
Self-documenting
text
String?
vs
text
String
communicates intent
IDE supportAutocomplete knows what can be null

Summary: Sound null safety means Dart's type system mathematically guarantees non-nullable variables can never be null -- there are no escape hatches. This eliminates an entire class of runtime crashes.

Additional Resources