Irbisa · Cheatsheet
Dart 3 Patterns & Records
Records, destructuring, switch expressions, if-case guards, sealed types, exhaustiveness
What is a record in Dart 3, and how do positional and named fields differ?
// The type is the shape: nothing declares (int, String) anywhere.(int, String) parse(String raw) => (raw.length, raw.toUpperCase()); typedef User = ({String name, int age}); // a typedef only names a shape void main() {…How do you destructure a record into variables, and where else can a destructuring pattern appear?
typedef User = ({String name, int age}); (int, String) fetch() => (200, 'ok'); void main() { final (code, body) = fetch(); // positional: bound by position…What is the difference between a
switchstatement and aswitchexpression in Dart 3?enum Status { active, blocked, deleted } // Expression: produces a value; arms are `pattern => value`.String label(Status s) => switch (s) { Status.active => 'Active', Status.blocked || Status.deleted => 'Unavailable',…What does this code print, and why does the last lookup miss even though the key looks identical?
void main() { print((1, 'a') == (1, 'a')); print((x: 1, y: 2) == (y: 2, x: 1)); print((1, 2) == (x: 1, y: 2)); final cache = <(int, String), String>{(1, 'en'): 'Hello'}; print(cache[(1, 'en')]); final byTags = <(List<String>, int), String>{(['a'], 1): 'x'}; print(byTags[(['a'], 1)]);}When would you return a record instead of a class or a
Map, and when is a record the wrong choice?// wrong: every caller re-guesses the keys and castsMap<String, dynamic> loadOld() => {'items': <String>[], 'total': 0}; // right: the shape is part of the signature(List<String> items, int total) loadPage() => (['a', 'b'], 42);…What is pattern matching in Dart 3, and which older idioms does it replace?
// before: is-checks, casts and nested conditionsString describeOld(Object json) { if (json is Map<String, dynamic>) { final user = json['user']; if (user is Map<String, dynamic> && user['name'] is String) { return 'user ${user['name'] as String}';…What is a
sealedclass, how does it enable an exhaustiveswitch, and why is it better than an abstract class for modelling state?sealed class AuthState {} final class SignedOut extends AuthState {} final class SigningIn extends AuthState {}…What does
if (value case pattern when condition)do, and what does it replace?class Session { String? token;} // before: two reads of a mutable field and a `!`void sendOld(Session s) {…What does an object pattern like
Cart(total: > 100)check, and what code does it run?class Item { const Item(this.price); final int price;} class Cart {…How does a list pattern like
[first, ..., last]match, and what does a named rest element...final restcost?String summary(List<String> names) => switch (names) { [] => 'nobody', [final only] => only, [final a, final b] => '$a and $b', [final first, ..., final last] => '$first … $last', // nothing copied };…How does a map pattern match decoded JSON, and what happens with extra keys or a missing key?
String greet(Map<String, Object?> json) { if (json case {'type': 'user', 'name': final String name, 'age': final int age}) { return '$name, $age'; // extra keys in json are ignored } if (json case {'type': 'user', 'name': final String name}) {…What are relational and logical patterns, and when do you need a
whenguard instead?const adult = 18;const retirement = 65; String bracket(int age) => switch (age) { < 0 => 'invalid', < adult => 'minor',…What does this program print, and why does the order of the
switcharms matter?String describe(Object o) => switch (o) { int n when n > 10 => 'big int', int() => 'int', num n => 'num $n', String(isEmpty: true) => 'empty string', String s => 'string $s', _ => 'other', }; void main() { for (final v in [42, 7, 2.5, '', 'hi', true]) { print(describe(v)); }}Why does this
switchfail to compile even though every subtype ofResultappears in it?sealed class Result {} final class Ok extends Result { Ok(this.value); final int value;} final class Err extends Result { Err(this.message); final String message;} String show(Result r) => switch (r) { Ok(:final value) when value > 0 => 'positive $value', Err(:final message) => 'error: $message', };Find the bug: the loading label never appears; users see "Something went wrong" while data is still loading.
sealed class LoadState {}final class Loading extends LoadState {}final class Loaded extends LoadState { Loaded(this.items); final List<String> items;}final class Failed extends LoadState {} String label(LoadState state) => switch (state) { Loading => 'Loading…', Loaded(:final items) => '${items.length} items', _ => 'Something went wrong', };Find the bug: some products from the API are rejected as invalid, although their JSON looks correct.
class Product { Product(this.name, this.price); final String name; final double price;} // `json` comes straight from `jsonDecode(response.body)`Product? parseProduct(Map<String, Object?> json) { if (json case {'name': String name, 'price': double price}) { return Product(name, price); } return null; // reported upstream as "invalid product"}Besides records and patterns, which Dart 3.x language features have you used, and what did each one replace?
// Needs `sdk: ^3.13.0` in pubspec.yaml.enum Side { start, center, end } class Point(final int x, final int y); // 3.13: constructor and fields in the header extension type UserId(String value) {} // 3.3: a zero-cost typed wrapper…What happens when you add a subtype to a public
sealedclass, and how do you ship that change safely?What do records and patterns cost at runtime: what allocates, and what code runs during matching?