Что такое Dart и каковы его основные особенности?
Dart — строго типизированный язык Google для Flutter. Ключевые особенности: sound null safety, изоляты вместо потоков, AOT+JIT компиляция, встроенный async/await через Future/Stream.
Что такое Dart
Dart — строго типизированный объектно-ориентированный язык от Google, оптимизированный для клиентской разработки. Его главная область применения — Flutter, где он компилируется в нативный ARM/x86-код через AOT-компиляцию для продакшена и работает через JIT во время разработки, обеспечивая hot reload. Для веба Dart компилируется в JavaScript через dart2js или dart2wasm.
Ключевые особенности языка
Sound null safety
Начиная с Dart 2.12 все типы по умолчанию non-nullable. Компилятор статически гарантирует, что переменная типа String никогда не будет null. Для nullable значений используется постфикс ?: String?. Это устраняет целый класс NPE-ошибок на этапе компиляции, а не в рантайме.
Изоляты вместо потоков
Dart не использует shared-memory threads. Вместо этого каждый isolate имеет собственную кучу (heap) и общается с другими только через передачу сообщений (SendPort / ReceivePort). Внутри одного изолята выполнение однопоточное — event loop обрабатывает микрозадачи и события. Это делает гонки данных (data races) структурно невозможными внутри одного изолята.
async/await и Future/Stream
Асинхронность встроена в язык. Future<T> — одно значение в будущем, Stream<T> — последовательность значений. Ключевые слова async/await позволяют писать асинхронный код в линейном стиле без callback hell.
Компиляция
- AOT — для релизных Flutter-сборок: быстрый старт, предсказуемый перформанс.
- JIT — для разработки: горячая перезагрузка кода без перезапуска приложения.
- dart2js / dart2wasm — для веба.
Другие особенности
- Именованные и позиционные параметры, параметры по умолчанию.
- Extension methods — добавление методов к существующим типам без наследования.
- Records и pattern matching (Dart 3.0+).
- Собственный менеджер пакетов — pub (
pub.dev).
Пример
import 'dart:async';
// Sound null safety: name не может быть null
String greet(String name) => 'Hello, $name!';
// Nullable параметр с дефолтом
Future<String> fetchUser(String? id) async {
// id?.trim() — безопасный вызов на nullable
final resolved = id?.trim() ?? 'anonymous';
// Эмуляция сетевого запроса
await Future.delayed(const Duration(milliseconds: 100));
return resolved;
}
// Изолят для тяжёлых вычислений
import 'dart:isolate';
int heavyCompute(int n) {
// Блокирующая работа — безопасна в отдельном изоляте
return List.generate(n, (i) => i).fold(0, (a, b) => a + b);
}
void main() async {
print(greet('Flutter'));
// Output: Hello, Flutter!
final user = await fetchUser(' alice ');
print(user);
// Output: alice
// Запуск вычисления в изоляте
final result = await Isolate.run(() => heavyCompute(1000000));
print(result);
// Output: 499999500000
}
Подводные камни
- Блокировка event loop — синхронный тяжёлый код (парсинг JSON больших файлов, криптография) запускается в main isolate и замораживает UI. Всегда выносите в
Isolate.run()илиcompute(). - Путаница Future vs Stream —
Futureдаёт одно значение и завершается; подписавшийся после завершения получит кешированный результат.Streamможет быть broadcast или single-subscription — повторная подписка к single-subscription упадёт в рантайме. - Неотловленные исключения в Future — если Future не имеет
.catchErrorилиtry/catchв await-контексте, ошибка тихо теряется или падает вZone.current.handleUncaughtError. Всегда добавляйте обработку ошибок. - late без инициализации —
late String valueбросаетLateInitializationErrorпри обращении до присваивания. Не используйтеlateкак замену nullable, если инициализация может не произойти. - Изоляты не разделяют память — нельзя передать в
Isolate.run()замыкание, захватывающее non-sendable объекты (например, открытый файловый дескриптор). Передавайте только примитивы, List, Map или классы с@pragma('vm:isolate-unsendable')-исключениями. - AOT и tree shaking — в релизной сборке неиспользуемый код вырезается. Динамическое создание типов через
dart:mirrorsнедоступно в Flutter AOT — используйтеbuild_runnerи кодогенерацию. - Null safety и legacy-зависимости — пакет без null safety помечается как
unsound. Смешивание sound и unsound кода отключает часть статических гарантий компилятора. Проверяйте совместимость на pub.dev. - async в конструкторе невозможен — конструктор Dart не может быть
async. Паттерн обхода: статический фабричный методstatic Future<MyClass> create()или отдельный метод инициализации.
Common mistakes
- Сводить «Dart и каковы его основные особенности» к синтаксису и не объяснять event loop.
- Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии dart-1.
- Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.
What the interviewer is testing
- Формулирует точную модель для «Dart и каковы его основные особенности» и подтверждает ее корректным примером.
- Умеет связать ответ с Future и Stream, тестированием и отладкой на устройстве.
- Называет ограничения подхода dart-1, включая производительность, память и сопровождение.