Skip to content

FFI and Advanced Topics

Dart 2.12 introduced sound null safety. This is not a nullable annotation system bolted onto an Existing type system. It is a fundamental rewrite of the type hierarchy: Null is a subtype of Every type, but only of nullable types. The compiler and runtime together guarantee that a Non-nullable variable never holds null at runtime. This guarantee is sound — it holds across Function boundaries, class hierarchies, generic instantiations, and asynchronous code paths.

Definition. Soundness (in the type-theoretic sense) means: if the type checker accepts a Program, no runtime type error related to null dereference can occur. The guarantee is global — it Does not depend on the programmer annotating every variable correctly, because the type checker Enforces consistency at all boundaries.

Unsound null safety (e.g., TypeScript”s strictNullChecksKotlin’s platform types at interop Boundaries) means the compiler can miss cases. A variable declared non-nullable might still be null at runtime due to unchecked casts, interop boundaries, or generics erasure. Dart’s null Safety is sound because:

  1. Null is a proper subtype, not a special sentinel value.
  2. Generic types are reified — List<int> and List<int?> are distinct at runtime.
  3. Flow analysis is global across function boundaries (within compilation units).
  4. The runtime enforces null checks at trusted boundaries (e.g., native FFI).

Every type T in Dart 2.12+ exists in two forms: T (non-nullable) and T? (nullable). The Nullable form is a union type: T? is equivalent to T | Null.

String name = 'Dart'; // non-nullable, must be initialized to non-null
String? maybeName = null; // nullable, can hold String or null
String? alsoName; // nullable, implicitly initialized to null
int count = 42; // non-nullable
int? maybeCount; // nullable, implicitly null
List<String> names = ['a']; // non-nullable list, must be non-null, elements non-null
List<String?> mixed = ['a', null]; // non-nullable list, elements can be null
List<String>? maybeNames = null; // nullable list reference

Definition. The nullability hierarchy: Never is a subtype of NullWhich is a subtype of Every TWhich is a subtype of T?Which is a subtype of Object?. Object is the supertype of All non-nullable types. Object? is the top type.

Never (bottom — no values inhabit this type)
|
Null (only value is null)
|
int, String, ... (non-nullable types)
|
Object (supertype of all non-nullable types)
|
Object? (top type — every value is an Object?)

The ! operator asserts to the type checker that an expression of type T? is non-null at this Point. It has no runtime effect other than throwing an AssertionError if the value is actually null.

String? getName() => 'Dart';
void example() {
String? name = getName();
// name has static type String?
String definite = name!; // static type is now String
// If name were null at runtime, throws: Null check operator used on a null value
}

When ! is safe:

// Safe: you just checked for null
String? name = getName();
if (name != null) {
// Flow analysis promotes name to String here — ! is unnecessary
print(name.length);
}
// Safe: the value is provably non-null from API contract
final list = <String?>['a', 'b', null];
String first = list.first!; // Safe if you know the list is non-empty and first is non-null

When ! is dangerous:

// Dangerous: depends on external state that could change
String? cached;
void init() {
cached = 'value';
}
String getValue() => cached!; // Crashes if init() was never called
// Dangerous: depends on list contents you don't control
List<String?> external = fetchFromNetwork();
String first = external.first!; // Crashes if first element is null
  • Dart Classes and Objects — Null safety and type system features build on the class fundamentals covered in OOP.
  • Dart Asynchronous Programming — Isolates and async patterns are both approaches to concurrent programming in Dart.
  • Rust Ownership — Rust’s ownership model provides memory safety without garbage collection, contrasting with Dart’s approach.
  • Java GraalVM — Both GraalVM native images and Dart FFI address native code interoperability.