Паттерны и switch в Dart 3
О чём эта статья
Dart 3 — сопоставление с образцом: switch как выражение, record, списки, sealed class. Компилятор проверяет исчерпывающие ветки.
Опора: типы, управление.
Паттерны и switch в Dart 3
Начиная с Dart 3, оператор switch перестал быть только «выбором по константе». Он стал частью системы сопоставления с образцом (pattern matching): можно разбирать значения по форме — константа, тип, поля record, элементы списка, ключи словаря. Компилятор проверяет исчерпывающий разбор для sealed-иерархий и расширенных enum.
Связанные темы: управляющие конструкции, типы и record, классы и ООП.
Switch statement и switch expression
Оператор switch выполняет ветку и завершает её без break (в отличие от C/Java fall-through запрещён):
enum Status { pending, done, failed }
String label(Status s) {
switch (s) {
case Status.pending:
return 'В очереди';
case Status.done:
return 'Готово';
case Status.failed:
return 'Ошибка';
}
}
Выражение switch возвращает значение — удобно присваивать результат:
String label(Status s) => switch (s) {
Status.pending => 'В очереди',
Status.done => 'Готово',
Status.failed => 'Ошибка',
};
Обе формы требуют, чтобы каждая ветка завершалась значением (в expression) или управляющим переходом (в statement).
Сопоставление с константами и типами
Паттерн может быть литералом, константой или проверкой типа:
String describe(Object value) => switch (value) {
0 => 'ноль',
int n when n < 0 => 'отрицательное: $n',
int n => 'целое: $n',
String s when s.isEmpty => 'пустая строка',
String s => 'строка: $s',
_ => 'что-то ещё',
};
when— дополнительное условие (guard) после сопоставления формы._— подстановочный паттерн «любое значение», обычно в последней ветке.
Оператор is внутри switch по-прежнему работает в обычном коде; в Dart 3 типовой паттерн int n одновременно проверяет тип и вводит переменную.
Record и именованные поля
Record удобно разбирать по позиции или по именам:
(String, int) parseId(String raw) {
final parts = raw.split(':');
return (parts[0], int.parse(parts[1]));
}
String format((String, int) pair) => switch (pair) {
('', _) => 'пустой ключ',
(var name, var age) when age < 0 => 'некорректный возраст для $name',
(var name, var age) => '$name — $age лет',
};
void demo() {
final named = (name: 'Анна', score: 90);
final msg = switch (named) {
(name: 'Анна', score: >= 85) => 'отлично, $name',
(name: var n, score: var s) => '$n: $s баллов',
};
print(msg);
}
Синтаксис (name: var n, score: var s) связывает именованные поля record с локальными переменными в ветке.
Списки и Map
Список — по длине и элементам:
String summarize(List<int> data) => switch (data) {
[] => 'нет данных',
[var only] => 'одно значение: $only',
[var first, var second, ...] => 'начало: $first, $second, всего ${data.length}',
};
... — rest-паттерн: «остальные элементы» (имя после ... опционально).
Map — по набору ключей:
String role(Map<String, String> user) => switch (user) {
{'role': 'admin', 'name': var n} => 'администратор $n',
{'role': var r, 'name': var n} => '$n ($r)',
_ => 'неизвестный профиль',
};
Порядок веток важен: более конкретные паттерны должны идти раньше общих.
Sealed class и исчерпывающий switch
sealed class ограничивает наследников одним файлом (или библиотекой). Компилятор знает полный набор подтипов и требует обработать каждый в switch:
sealed class Result<T> {}
final class Ok<T> extends Result<T> {
final T value;
Ok(this.value);
}
final class Err<T> extends Result<T> {
final String message;
Err(this.message);
}
String explain(Result<int> r) => switch (r) {
Ok(value: var v) => 'успех: $v',
Err(message: var m) => 'ошибка: $m',
};
Если добавить новый подкласс Result и забыть ветку в switch, анализатор выдаст предупреждение. Тот же приём применяют для enhanced enum с полями и для иерархий состояний UI (загрузка / данные / ошибка).
Объекты и деструктуризация
Для обычных классов согласованность паттерна зависит от того, поддерживает ли тип сопоставление (часто через enum или record внутри). Практичный приём — хранить варианты в sealed + final class или возвращать record из функции:
({bool ok, String text}) load() {
// ...
return (ok: true, text: 'готово');
}
void handle() {
switch (load()) {
case (ok: true, text: var t):
print(t);
case (ok: false, text: var e):
print('сбой: $e');
}
}
Связь с null safety
Паттерны учитывают nullable-типы. Сопоставление case null: отделяет отсутствие значения; для полей record типа String? guard when помогает сузить тип:
String? title;
final display = switch (title) {
null => '(без названия)',
var t when t.length > 40 => '${t.substring(0, 40)}…',
var t => t,
};
Операторы ?., ?? из статьи про операторы остаются для простых случаев; switch с паттернами уместен, когда вариантов несколько и логика ветвления объёмная.
Когда что выбирать
| Задача | Инструмент |
|---|---|
| Два–три исхода, одно выражение | Тернарный ? : или ?? |
| Enum или sealed-иерархия | switch expression |
| Разбор JSON-подобной структуры (record / Map) | Паттерны в switch |
Длинная цепочка if / else if по типу | switch с типовыми паттернами |
Паттерны не заменяют полиморфизм методов: поведение, привязанное к классу, по-прежнему оформляют методами и миксинами. switch уместен там, где нужно явно перечислить формы данных на границе слоя (парсинг, маппинг API, reducer состояния).
Практическая заметка для Flutter
В UI часто встречается разбор AsyncSnapshot, union-состояний загрузки или кодов ответа API. switch expression с sealed типами делает дерево виджетов читаемым: каждая ветка — отдельный подвиджет без вложенных if. Подробнее о приложениях — в статье про Flutter.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). История Dart - возникновение языка в Google и его развитие как основы для Flutter-экосистемы. Инкапсуляция через _, late, конструкторы, factory, const, абстрактные классы, миксины и интерфейсы в Dart. Простые приложения на Dart — CLI, файлы, JSON и минимальный HTTP на dart:io. Dart выглядит как Java или C#, но есть свои привычки: точка с запятой в конце строки не обязательна, строки в "...", многострочные литералы '''. Основы Dart - точка входа `main`, базовый синтаксис и старт построения приложений на языке. Runtime Dart — система типов, isolates, event loop, JIT/AOT, сборка мусора, SDK и связка с Flutter. Flutter - экосистема Dart для кроссплатформенной UI-разработки, сборки и тестирования приложений. Типизация, набор правил определения типа данных значений языка. Условия, циклы, switch, арифметика, логика и null-aware операторы Dart — с примерами для Dart 3. Функция — это именованный фрагмент кода, который принимает входные данные, выполняет определённую последовательность действий и может возвращать результат. Гайд по установке и настройке с написанием первой программы и её запуском. Консольный ввод-вывод, работа с файлами, JSON и базовый HTTP-клиент и сервер через dart:io — Dart вне Flutter.История языка Dart
Классы и ООП в Dart
Простые приложения на Dart
Синтаксис и пунктуация в Dart
Основы языка Dart
Архитектура платформы Dart
Flutter
Типы данных и безопасность типов
Управляющие конструкции и операторы Dart
Функции и асинхронность
Первая программа на Dart
Консоль, файлы и HTTP в Dart