Основы языка Dart
Основы языка Dart
Что такое Dart?
Dart — это язык программирования со следующими особенностями:
- Типизация — статическая, сильная, звуковая (sound null safety с Dart 2.12); автоматический вывод типов (
var, inferred types). - Парадигма — объектно-ориентированный (всё — объект), функциональный (функции первого класса, методы высшего порядка для коллекций), императивный.
- Уровень — высокоуровневый.
- Выполнение — компилируемый — JIT (Dart VM, разработка), AOT (нативный машинный код), транспиляция в JavaScript (
dart compile js). - Память — автоматическая (генерационный GC).
- Платформа — кроссплатформенный (мобильные, десктоп, веб, сервер); управляемый runtime (Dart VM); AOT в нативный код; транспиляция в JavaScript для браузера.
- Формат разработки — один файл можно запустить (
dart run file.dart), но идиоматично — структура проекта сpubspec.yamlиpub. - Направление — кроссплатформенный UI (Flutter), мобильная и десктопная разработка, веб, бэкенд (Shelf, Dart Frog), CLI-утилиты.
- REPL — нет классического интерактивного REPL в CLI; интерактивность через DartPad (веб), hot reload в Flutter/Dart VM, отладчик в IDE.
- Поколение — современный (с 2011), активно развивающийся (Dart 3).
- Параллелизм и асинхронность — однопоточный event loop в изоляте; нативная асинхронность (
async/await,Future,Stream); параллелизм через изоляты (обмен сообщениями, без shared memory). - Безопасность — относительно безопасный — sound null safety, статическая типизация, нет арифметики указателей; зона "опасности" —
dart:ffiи небезопасный код через interop.
Если какой-то пункт из списка непонятен — подробные определения и примеры в Язык программирования.
Скелет программы — main, переменные, встроенные типы, функции. Flutter пока в стороне — тот же void main() будет и во Flutter.
Дальше: синтаксис → типы → async → первая программа.
Dart — это современный язык программирования, созданный компанией Google и предназначенный для разработки высокопроизводительных, масштабируемых и кроссплатформенных приложений. Язык сочетает в себе простоту синтаксиса, строгую типизацию, объектно-ориентированную архитектуру и мощные инструменты для асинхронного программирования. Dart получил широкое распространение благодаря фреймворку Flutter, который позволяет создавать нативные мобильные, десктопные и веб-приложения из единой кодовой базы. Однако сам язык не ограничивается экосистемой Flutter и может использоваться самостоятельно для серверной разработки, скриптов, утилит командной строки и других задач.
Кратко о языке
Dart — open-source язык (лицензия BSD), развиваемый Google и сообществом. Сегодня он чаще всего используется вместе с Flutter, но подходит и для консольных утилит, серверов и скриптов. Хронологию версий, null safety и роль Flutter см. в статье История языка Dart.
Синтаксис и структура программы
Программа на Dart начинается с точки входа — функции main. Эта функция вызывается автоматически при запуске приложения и служит отправной точкой для выполнения кода. Простейшая программа выглядит так:
void main() {
print('Привет, Вселенная IT!');
}
Разбор:
void main()— стандартная точка входа Dart-программы.print(...)выводит строку в консоль.- Кавычки
'...'задают литерал типаString. - Это минимальный скелет приложения без классов и зависимостей.
Ключевое слово void указывает, что функция ничего не возвращает. Функция print выводит текст в консоль. Такой минималистичный пример демонстрирует явное объявление функций, фигурные скобки для тела блока и опциональные точки с запятой в конце строк (в Dart 3 их обычно опускают).
Dart следует принципам C-подобного синтаксиса — операторы, условия, циклы и объявления функций используют привычные конструкции, такие как if, for, while, return. Это делает язык доступным для разработчиков, имеющих опыт в Java, C#, JavaScript или TypeScript.
Типы данных и переменные
Dart — язык со статической типизацией, но он также поддерживает вывод типов. Это означает, что разработчик может явно указывать тип переменной, а может позволить компилятору определить его автоматически на основе значения.
Переменные объявляются с помощью ключевых слов var, final или const, а также с указанием конкретного типа:
var name = 'Dart';— компилятор выводит, чтоnameимеет типString.String language = 'Dart';— тип указан явно.final version = '3.0';— переменная инициализируется один раз и не может быть изменена позже.const pi = 3.14159;— константа, значение которой известно на этапе компиляции и неизменно в течение всего времени выполнения.
Основные встроенные типы в Dart включают:
int— целые числа, например,42.double— числа с плавающей точкой, например,3.14.bool— логический тип со значениямиtrueиfalse.String— последовательности символов в одинарных или двойных кавычках, поддерживающие интерполяцию:'Значение: $pi'.List— упорядоченная коллекция элементов, например,[1, 2, 3].Set— неупорядоченная коллекция уникальных элементов.Map— ассоциативный массив, хранящий пары "ключ — значение".
Все эти типы являются объектами, даже числа и логические значения. Это означает, что у любого значения можно вызывать методы и обращаться к свойствам. Например, строка 'привет'.toUpperCase() возвращает 'ПРИВЕТ'.
Функции
Функции в Dart — это полноправные объекты первого класса. Их можно присваивать переменным, передавать как аргументы другим функциям и возвращать из функций. Объявление функции включает тип возвращаемого значения, имя, список параметров и тело.
Пример функции с явным возвратом:
int add(int a, int b) {
return a + b;
}
Разбор:
intв сигнатуре задаёт тип возвращаемого значения и типы параметров.return a + bзавершает функцию и отдаёт сумму вызывающему коду.- Позиционные параметры передаются строго в объявленном порядке.
Dart поддерживает сокращённый синтаксис для функций, состоящих из одного выражения:
int multiply(int a, int b) => a * b;
Разбор:
- Стрелочная форма
=>эквивалентна{ return ...; }для одного выражения. - Результат выражения
a * bавтоматически становится возвращаемым значением. - Такой синтаксис удобен для коротких математических и преобразующих функций.
Стрелочная нотация (=>) эквивалентна записи с return и фигурными скобками, но короче и читабельнее для простых операций.
Функции могут иметь именованные параметры, которые передаются по ключу, а не по позиции. Это повышает читаемость вызовов:
void greet({required String name, String? greeting}) {
print('${greeting ?? 'Привет'}, $name!');
}
Разбор:
- Параметры в
{...}— именованные: при вызове указываютname:иgreeting:. requiredделаетnameобязательным именованным аргументом.String? greetingдопускаетnull, если приветствие не передали.greeting ?? 'Привет'подставляет значение по умолчанию.${...}в строке — интерполяция выражения.
Здесь name — обязательный именованный параметр, а greeting — необязательный, который может быть null. Оператор ?? предоставляет значение по умолчанию, если переменная равна null.
Классы и объектно-ориентированное программирование
Dart — полностью объектно-ориентированный язык. Все значения являются экземплярами классов, и каждый класс наследуется от корневого класса Object.
Класс объявляется с помощью ключевого слова class. Он может содержать поля, методы, конструкторы и геттеры/сеттеры:
import 'dart:math';
class Point {
final double x;
final double y;
Point(this.x, this.y);
double distanceToOrigin() => sqrt(x * x + y * y);
}
Разбор:
import 'dart:math'подключает функциюsqrt.final double x, yфиксирует координаты точки после создания.Point(this.x, this.y)инициализирует поля через параметры конструктора.distanceToOrigin()вычисляет расстояние до(0, 0)по теореме Пифагора.
Конструктор Point(this.x, this.y) — синтаксический сахар: параметры сразу присваиваются полям. Функция sqrt из библиотеки dart:math вычисляет расстояние до начала координат (у типа double нет метода .sqrt()).
Dart поддерживает наследование, абстрактные классы, интерфейсы и миксины. Интерфейсы реализуются неявно: любой класс автоматически определяет интерфейс, содержащий все его публичные методы и свойства. Это позволяет легко применять полиморфизм без необходимости явного объявления interface. Подробнее о _, late, factory и sealed: классы и ООП.
Миксины — это способ повторного использования кода в нескольких иерархиях наследования. Они позволяют добавлять функциональность классу без создания жёсткой связи через наследование:
mixin Flyable {
void fly() => print('Лечу!');
}
class Bird with Flyable {}
Разбор:
mixin Flyableописывает переиспользуемое поведение без наследования класса.class Bird with Flyableподмешивает методfly()в классBird.- Экземпляр
Bird()получает методfly, хотяFlyableне является родителем.
Экземпляр Bird теперь обладает методом fly, несмотря на то, что Flyable не является классом-родителем.
Асинхронное программирование
Одной из ключевых особенностей Dart является встроенная поддержка асинхронного выполнения кода. Язык предоставляет элегантные и читаемые механизмы для работы с операциями, которые не завершаются мгновенно — сетевыми запросами, чтением файлов, взаимодействием с базами данных или ожиданием пользовательского ввода. Вместо блокировки основного потока выполнения, Dart использует модель событий и управление через объекты Future и ключевые слова async / await.
Объект Future представляет собой обещание получить значение в будущем. Он может находиться в одном из трёх состояний: незавершённом, успешно завершённом или завершённом с ошибкой. Функция, возвращающая Future, помечается как async. Внутри такой функции можно использовать await, чтобы дождаться результата асинхронной операции, не прерывая читаемость кода:
import 'package:http/http.dart' as http;
Future<String> fetchUserData() async {
final response = await http.get(Uri.parse('https://api.example.com/user'));
if (response.statusCode != 200) {
throw Exception('HTTP ${response.statusCode}');
}
return response.body;
}
Разбор:
Future<String>означает асинхронный результат типаString.asyncпозволяет использоватьawaitвнутри функции.http.get(...)выполняет HTTP GET и возвращаетFuture<Response>.- Проверка
statusCode != 200отсекает неуспешные ответы черезthrow. response.bodyвозвращает текст ответа какString.
В pubspec.yaml нужна зависимость http. Такой код читается почти как синхронный: await приостанавливает только текущую async-функцию, не блокируя event loop изолята.
Stream — последовательность событий во времени (клики, WebSocket, датчики). Генераторы потоков пишут через async* и yield:
Stream<int> counter(int max) async* {
for (var i = 1; i <= max; i++) {
await Future.delayed(const Duration(seconds: 1));
yield i;
}
}
Разбор:
Stream<int>описывает поток целых значений во времени.async*делает функцию генератором потока.await Future.delayed(...)имитирует паузу между событиями.yield iотправляет очередное значение подписчикам stream.
Обработка ошибок
Интерактивное демо — часть сценариев на Python (
try/except); в Dart —try/catch/finally, но стек вызовов и раскрутка те же. Подробнее: ошибки и исключения.
Play ITЗагрузка интерактивного демо…
Dart использует механизм исключений, аналогичный другим современным языкам. Ошибки возникают с помощью ключевого слова throw, перехватываются конструкцией try-catch, а необязательный блок finally выполняется в любом случае — успешно завершился код или произошла ошибка. Исключения в Dart не требуют объявления в сигнатуре функции, что упрощает проектирование API.
try {
var result = await riskyOperation();
print('Успех: $result');
} on Exception catch (e) {
print('Ошибка запроса: $e');
} catch (e) {
print('Неизвестная ошибка: $e');
} finally {
cleanupResources();
}
Разбор:
tryсодержит основную логику, где может возникнуть ошибка.on Exception catch (e)перехватывает только исключения типаException.- Общий
catch (e)ловит остальные ошибки. finallyвыполняется всегда — удобно для освобождения ресурсов (cleanupResources).
Такой подход обеспечивает гибкость и предсказуемость при работе с ненадёжными операциями.
Коллекции и работа с данными
Dart предоставляет богатый набор встроенных коллекций: списки (List), множества (Set) и словари (Map). Все они поддерживают литералы, методы высшего порядка и функциональные преобразования. Например, список чисел можно фильтровать, преобразовывать и сворачивать одной цепочкой вызовов:
var numbers = [1, 2, 3, 4, 5];
var doubledEvens = numbers
.where((n) => n.isEven)
.map((n) => n * 2)
.toList();
Разбор:
where((n) => n.isEven)оставляет только чётные числа.map((n) => n * 2)удваивает каждый оставшийся элемент.toList()материализует ленивую цепочку в новыйList.- Для
[1,2,3,4,5]результат —[4, 8](чётные 2 и 4, удвоенные).
Результатом будет [4, 8]. Такие операции делают обработку данных выразительной и лаконичной. Dart также поддерживает расширения коллекций через spread-оператор (...) и условные элементы (if внутри литерала), что особенно полезно при построении UI в Flutter.
Null safety
Начиная с версии 2.12, Dart включает систему звуковой null safety — механизм, который гарантирует отсутствие ошибок, связанных с обращением к null. Каждый тип в Dart теперь либо допускает значение null, либо нет. Тип String не может быть null, а String? — может. Компилятор проверяет все возможные пути выполнения и требует явной обработки случаев, когда значение может отсутствовать.
Это достигается за счёт трёх основных принципов:
- Non-nullable by default: переменные без знака вопроса не могут содержать
null. - Flow analysis — компилятор отслеживает, где значение уже проверено на
null, и разрешает его использование. - Required initialisation: все поля класса должны быть инициализированы до того, как объект станет доступен.
Null safety резко снижает количество runtime-ошибок и делает код более надёжным без необходимости вручную проверять каждую переменную.
Изоляты и конкурентность
Dart однопоточный по своей природе, но поддерживает конкурентность через изоляты — независимые рабочие единицы, которые не разделяют память. Каждый изолят имеет собственную память и очередь событий, а обмен данными между ними происходит только через передачу сообщений. Это исключает гонки данных и упрощает написание параллельного кода.
Изоляты особенно полезны для выполнения тяжёлых вычислений, которые могут заблокировать основной поток UI. Например, в мобильном приложении на Flutter фоновый изолят может обрабатывать изображение, не замедляя интерфейс.
Инструментарий и экосистема
Dart поставляется с мощным набором инструментов: компилятором, виртуальной машиной (Dart VM), пакетным менеджером pub и интеграцией с популярными IDE. Пакеты Dart публикуются в реестре pub.dev, где доступны тысячи библиотек — от утилит для работы с датами до полноценных фреймворков.
Компиляция Dart возможна в несколько целей:
- JIT (Just-In-Time) — для быстрой разработки с горячей перезагрузкой.
- AOT (Ahead-Of-Time) — для выпуска нативных приложений с высокой производительностью.
- JavaScript — для запуска в браузере.
Эта гибкость делает Dart универсальным языком, подходящим как для клиентской, так и для серверной разработки.
Модульность и организация кода
Dart поощряет чёткое разделение кода на логические единицы — библиотеки. Каждый файл Dart автоматически является библиотекой, даже если в нём не указано явное объявление library. Это позволяет легко импортировать функциональность из других файлов с помощью директивы import. Например, чтобы использовать математические функции или коллекции, достаточно написать:
import 'dart:math';
import 'dart:collection';
Разбор:
dart:mathдаёт математические функции (sqrt,powи др.).dart:collectionсодержит расширенные структуры коллекций.- Импорты SDK подключаются без
pubspec.yaml.
Стандартная библиотека Dart разделена на тематические модули — dart:core содержит базовые типы и функции, dart:async — инструменты для асинхронности, dart:io — операции ввода-вывода, dart:convert — кодирование и декодирование данных. Все эти модули доступны без установки дополнительных пакетов.
Помимо стандартных библиотек, Dart поддерживает импорт сторонних пакетов через pubspec.yaml — файл конфигурации проекта. После добавления зависимости и выполнения команды dart pub get, любой компонент пакета становится доступен для импорта:
import 'package:http/http.dart' as http;
Разбор:
package:http/...— импорт внешней зависимости изpub.dev.as httpзадаёт префикс для вызовов (http.get,http.post).- Так снижается риск конфликта имён между библиотеками.
Ключевое слово as создаёт псевдоним, что помогает избежать конфликтов имён. Также можно использовать show и hide, чтобы импортировать только нужные части библиотеки или скрыть нежелательные:
import 'utils.dart' show formatDate, validateEmail;
Разбор:
show formatDate, validateEmailимпортирует только перечисленные символы.- Это уменьшает "засорение" пространства имён в файле.
- Альтернатива —
hide, если нужно импортировать всё, кроме части API.
Такой подход повышает читаемость и уменьшает объём загружаемого кода.
Генерики и параметризованные типы
Dart поддерживает обобщённое программирование через генерики. Это позволяет создавать классы, интерфейсы и функции, которые работают с любыми типами, сохраняя при этом безопасность типов. Например, список целых чисел объявляется как List<int>, а словарь строковых ключей и значений — как Map<String, String>.
Генерики особенно полезны при создании переиспользуемых структур данных. Рассмотрим простой контейнер:
class Box<T> {
final T value;
const Box(this.value);
T getValue() => value;
}
Разбор:
Box<T>— обобщённый класс с параметром типаT.- Поле
valueхранит данные конкретного типа, заданного при созданииBox. getValue()возвращает значение без приведения типов.
Здесь T — параметр типа. При создании экземпляра указывается конкретный тип:
var stringBox = Box<String>('Привет');
var numberBox = Box<int>(42);
Разбор:
Box<String>('Привет')создаёт контейнер только для строк.Box<int>(42)— отдельный контейнер только дляint.- Компилятор не позволит положить число в
stringBoxбез ошибки типизации.
Компилятор гарантирует, что в stringBox нельзя случайно поместить число, а метод getValue() всегда вернёт значение ожидаемого типа. Это устраняет необходимость в приведении типов и предотвращает ошибки на этапе выполнения.
Метапрограммирование и аннотации
Dart предоставляет ограниченные, но эффективные средства метапрограммирования через аннотации и рефлексию. Аннотации — это метаданные, которые можно применять к классам, методам, переменным и другим элементам кода. Они не влияют на выполнение программы напрямую, но могут использоваться инструментами во время сборки или анализа.
Например, аннотация @deprecated помечает устаревший API:
@deprecated
void oldMethod() {
// ...
}
Разбор:
@deprecatedпомечает API как устаревший для анализатора и IDE.- Сам по себе код выполняется, но инструменты предупреждают разработчика.
- Аннотации часто используют генераторы кода и линтеры на этапе сборки.
Современный подход к метапрограммированию в Dart — это code generation. Специальные генераторы, запускаемые через build_runner, анализируют исходный код и создают дополнительные файлы с шаблонным кодом — сериализаторы, мапперы, адаптеры. Это позволяет избежать ручного написания повторяющейся логики и сохранить производительность, так как всё генерируется на этапе компиляции, а не во время выполнения.
Стандартная библиотека и её возможности
Стандартная библиотека Dart — это фундамент, на котором строятся все приложения. Она включает в себя не только базовые типы, но и мощные утилиты для работы с датами, регулярными выражениями, URI, JSON, файловой системой и сетевыми соединениями. Все эти компоненты тщательно оптимизированы и документированы.
Работа с JSON, например, осуществляется через функции jsonEncode и jsonDecode из dart:convert. Хотя Dart не имеет встроенной поддержки автоматической сериализации объектов, паттерны преобразования легко реализуются вручную или с помощью генераторов кода.
Для работы с датами и временем используется класс DateTime, который поддерживает создание, сравнение, форматирование и арифметические операции. Регулярные выражения представлены классом RegExp, совместимым с большинством современных движков.
Особенности Dart в контексте Flutter
Хотя Dart — самостоятельный язык, его наибольшее распространение получило в связке с Flutter. В этом контексте многие особенности языка раскрываются особенно ярко. Декларативный стиль UI, реактивность, горячая перезагрузка, эффективное управление состоянием — всё это опирается на возможности Dart — быструю компиляцию, null safety, асинхронность, генерики и работу с коллекциями.
Например, виджеты в Flutter — это обычные классы Dart. Их дерево строится с помощью конструкторов и литералов, а изменения состояния вызывают пересоздание частей дерева. Благодаря эффективному diff-алгоритму и компиляции в нативный код, такие операции выполняются мгновенно.
Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.