Type System

Dart's type system — classes, enums, sealed classes, records, generics, and pattern matching

Dart’s type system combines static typing with null safety and pattern matching (Dart 3+).

Records (Dart 3)

Immutable data structures with named or positional fields:

// Positional record
var point = (3, 4);
print(point.$1);  // 3
print(point.$2);  // 4

// Named record
var user = (name: 'Ada', age: 30);
print(user.name);  // Ada
print(user.age);   // 30

// Mixed
var mixed = ('hello', count: 5);

// Type aliases for records
typedef Point = (double x, double y);
Point origin = (0.0, 0.0);

// Destructuring
var (x, y) = point;
var (name: n, age: a) = user;

Sealed classes (Dart 3)

Restricted class hierarchies — the compiler knows all subtypes. Enables exhaustive pattern matching.

sealed class Result<T> {}

class Success<T> extends Result<T> {
  final T value;
  Success(this.value);
}

class Error extends Result<Never> {
  final String message;
  Error(this.message);
}

class Loading extends Result<Never> {}

// Exhaustive switch — no default needed
String describe(Result<int> result) => switch (result) {
  Success(value: var v) => 'Success: $v',
  Error(message: var m) => 'Error: $m',
  Loading() => 'Loading...',
};

Enum classes

enum Status { pending, active, done }

var s = Status.active;
s.name;                    // 'active'
s.index;                   // 1
Status.values;              // [Status.pending, Status.active, Status.done]

// Enhanced enums
enum Vehicle {
  car(tires: 4, speed: 120),
  bicycle(tires: 2, speed: 25),
  motorcycle(tires: 2, speed: 180);

  final int tires;
  final int speed;
  const Vehicle({required this.tires, required this.speed});

  String get description => '$name has $tires tires, top speed $speed mph';
}

Vehicle.car.description;  // 'car has 4 tires, top speed 120 mph'

Pattern matching (Dart 3)

// Switch expressions
var label = switch (status) {
  Status.pending => 'Pending',
  Status.active => 'Active',
  Status.done => 'Done',
};

// Type patterns
String describe(Object value) => switch (value) {
  int i => 'int: $i',
  String s => 'string: $s',
  List<int> list => 'int list of length ${list.length}',
  _ => 'unknown',
};

// Guard clauses
switch (value) {
  case int i when i > 0:
    print('positive: $i');
  case int i:
    print('non-positive: $i');
}

// Destructuring records
var (name, age) = getUser();

// Destructuring in for-in
for (var (x, y) in points) {
  print('($x, $y)');
}

Generics

// Generic class
class Box<T> {
  final T value;
  Box(this.value);
}

var intBox = Box(42);       // Box<int>
var strBox = Box('hello');   // Box<String>

// Generic function
T identity<T>(T value) => value;

// Generic with bounds
class Repository<T extends Entity> {
  final List<T> items = [];
  void add(T item) => items.add(item);
}

// Covariant generics
// Box<int> is a subtype of Box<num> when used in read positions

Typedefs

typedef IntList = List<int>;
typedef MapCallback = void Function(String key, int value);

IntList numbers = [1, 2, 3];
MapCallback callback = (key, value) => print('$key: $value');

// Generic typedef
typedef Mapper<T, R> = R Function(T value);
Mapper<int, String> intToString = (n) => n.toString();

Extension types (Dart 3.3+)

Create lightweight wrappers without allocation overhead:

extension type Password(String value) {
  bool get isValid => value.length >= 8;
  bool get hasUppercase => value.contains(RegExp(r'[A-Z]'));
}

extension type UserId(int id) {
  String get asString => id.toString();
}

var pass = Password('MySecret123');
pass.isValid;        // true
pass.value;          // 'MySecret123'  can still access underlying value