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 positionsTypedefs
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