LEARN X · ЗА 15 МИН

Dart

Экспресс-тур по Dart: переменные, null-безопасность, коллекции, функции, классы, миксины, async/await и дженерики — весь язык на одной странице.

Dart — язык от Google, на котором пишут приложения на Flutter. Строго типизированный, с надёжной null-безопасностью и компиляцией в нативный код и JavaScript. Ниже — весь язык на одной странице: почти вся теория спрятана в комментариях рабочего кода.

1. Структура программы

Точка входа — функция main. Вывод в консоль — print.

// Однострочный комментарий
/* Многострочный
   комментарий */
/// Doc-комментарий (документация)

// main — точка входа в программу
void main() {
  print('Привет, Dart!'); // вывод: Привет, Dart!

  // Точка с запятой обязательна в конце инструкции
  print(2 + 2); // 4
}

2. Переменные и типы

var — вывод типа, final — присвоение один раз, const — константа на этапе компиляции.

void main() {
  // var — тип выводится автоматически
  var name = 'Аня'; // String
  var age = 25;     // int

  // Явные типы
  int year = 2026;        // целое
  double pi = 3.14;       // число с плавающей точкой
  bool isOk = true;       // логический тип
  String city = 'Москва'; // строка

  // num — общий тип для int и double
  num x = 5;   // ок
  x = 5.5;     // тоже ок

  // final — присваивается один раз (значение в рантайме)
  final now = DateTime.now();

  // const — константа времени компиляции (значение известно заранее)
  const maxUsers = 100;

  // dynamic — тип может меняться (отключает проверки типов)
  dynamic anything = 'строка';
  anything = 42; // допустимо

  print('$name, $age, $year, $pi, $isOk, $city, $x, $maxUsers, $anything');
  print(now);
}

3. Строки

Интерполяция через $переменная и ${выражение}.

void main() {
  var name = 'Дима';
  var age = 30;

  // Интерполяция переменной
  print('Имя: $name');           // Имя: Дима
  // Интерполяция выражения в ${ }
  print('Через год: ${age + 1}'); // Через год: 31

  // Одинарные и двойные кавычки равнозначны
  var s1 = 'одинарные';
  var s2 = "двойные";

  // Многострочная строка
  var multi = '''
строка 1
строка 2''';

  // Конкатенация
  var full = 'Привет, ' + name; // Привет, Дима
  // Соседние литералы склеиваются
  var joined = 'abc' 'def';     // abcdef

  // Методы строк
  print('dart'.toUpperCase());     // DART
  print('  обрезка  '.trim());     // 'обрезка'
  print('a,b,c'.split(','));       // [a, b, c]
  print('hello'.length);           // 5
  print('hello'.contains('ell'));  // true
  print('hello'.replaceAll('l', 'L')); // heLLo
  print('hello'[0]);               // h

  print('$s1 $s2 $multi $full $joined');
}

3.1. Числа: парсинг и форматирование

void main() {
  // Преобразование строки в число
  int n = int.parse('42');        // 42
  double d = double.parse('3.14'); // 3.14

  // Число в строку
  String s = 42.toString();        // '42'
  print(3.14159.toStringAsFixed(2)); // 3.14 (округление до 2 знаков)

  // Деление
  print(7 / 2);   // 3.5  (всегда double)
  print(7 ~/ 2);  // 3    (целочисленное деление)
  print(7 % 2);   // 1    (остаток)

  print('$n $d $s');
}

4. Null-безопасность

Главная фишка Dart: по умолчанию переменная не может быть null. Чтобы разрешить null — добавь ? к типу.

void main() {
  // int x = null;  // ОШИБКА компиляции: int не может быть null

  // int? — допускает null
  int? maybeAge = null;
  maybeAge = 25; // тоже можно

  String? name; // по умолчанию null

  // ?. — безопасный доступ: если null, вернёт null, без падения
  print(name?.length); // null (а не ошибка)

  // ?? — значение по умолчанию, если слева null
  int len = name?.length ?? 0;
  print(len); // 0

  // ??= — присвоить, только если переменная сейчас null
  name ??= 'Гость';
  print(name); // Гость

  // ! — оператор «я уверен, что не null» (бросит ошибку, если null)
  int? a = 5;
  int b = a!; // снимаем nullable, b = 5
  print(b);

  // late — переменная инициализируется позже, но до первого чтения
  late String greeting;
  greeting = 'Привет'; // присвоили перед использованием
  print(greeting); // Привет
}

5. Операторы и условия

void main() {
  var age = 18;

  // if / else if / else
  if (age < 18) {
    print('несовершеннолетний');
  } else if (age == 18) {
    print('ровно 18'); // сработает это
  } else {
    print('взрослый');
  }

  // Тернарный оператор
  var status = age >= 18 ? 'можно' : 'нельзя';
  print(status); // можно

  // Логические операторы: && (и), || (или), ! (не)
  print(age > 0 && age < 100); // true

  // switch — современный синтаксис (Dart 3)
  var day = 3;
  switch (day) {
    case 1:
      print('понедельник');
    case 6 || 7: // несколько значений через ||
      print('выходной');
    default:
      print('будний день'); // сработает это
  }

  // switch-выражение (возвращает значение)
  var name = switch (day) {
    1 => 'пн',
    3 => 'ср',
    _ => 'другой', // _ — любой остальной случай
  };
  print(name); // ср
}

6. Циклы

void main() {
  // Классический for
  for (int i = 0; i < 3; i++) {
    print(i); // 0, 1, 2
  }

  // for-in — перебор коллекции
  var fruits = ['яблоко', 'банан', 'груша'];
  for (var fruit in fruits) {
    print(fruit); // яблоко, банан, груша
  }

  // forEach
  fruits.forEach((f) => print(f));

  // while
  var n = 0;
  while (n < 3) {
    print('while $n');
    n++;
  }

  // do-while — тело выполнится хотя бы раз
  var m = 0;
  do {
    print('do $m');
    m++;
  } while (m < 2);

  // break и continue
  for (var i = 0; i < 5; i++) {
    if (i == 1) continue; // пропустить итерацию
    if (i == 3) break;    // выйти из цикла
    print('loop $i'); // loop 0, loop 2
  }
}

7. Коллекции

List (список), Map (словарь), Set (множество).

void main() {
  // List — упорядоченный список
  List<int> nums = [1, 2, 3];
  nums.add(4);            // [1, 2, 3, 4]
  print(nums[0]);         // 1
  print(nums.length);     // 4
  print(nums.first);      // 1
  print(nums.last);       // 4
  print(nums.contains(2)); // true

  // Map — пары ключ-значение
  Map<String, int> ages = {'Аня': 25, 'Дима': 30};
  print(ages['Аня']);     // 25
  ages['Маша'] = 22;      // добавление
  print(ages.keys);       // (Аня, Дима, Маша)
  print(ages.values);     // (25, 30, 22)

  // Set — уникальные значения
  Set<int> unique = {1, 2, 2, 3};
  print(unique); // {1, 2, 3} (дубликаты убраны)

  // Spread-оператор ... — раскрыть коллекцию
  var more = [0, ...nums]; // [0, 1, 2, 3, 4]
  print(more);

  // ...? — spread с защитой от null
  List<int>? maybe;
  var safe = [1, ...?maybe]; // [1]
  print(safe);

  // collection if — условное добавление элемента
  bool admin = true;
  var menu = ['Главная', if (admin) 'Админка'];
  print(menu); // [Главная, Админка]

  // collection for — генерация элементов в литерале
  var squares = [for (var i = 1; i <= 3; i++) i * i];
  print(squares); // [1, 4, 9]

  // map / where — преобразование и фильтрация
  print(nums.map((n) => n * 2).toList());     // [2, 4, 6, 8]
  print(nums.where((n) => n.isEven).toList()); // [2, 4]
}

8. Функции

// Обычная функция с типами
int add(int a, int b) {
  return a + b;
}

// Стрелочная функция (=> для одного выражения)
int square(int x) => x * x;

// Именованные параметры в { } — вызываются по имени
// required делает параметр обязательным
String greet({required String name, String greeting = 'Привет'}) {
  return '$greeting, $name!';
}

// Позиционные необязательные параметры в [ ] со значением по умолчанию
String tag(String text, [String wrap = '*']) {
  return '$wrap$text$wrap';
}

void main() {
  print(add(2, 3));   // 5
  print(square(4));   // 16

  // Именованные аргументы — порядок не важен
  print(greet(name: 'Аня'));                    // Привет, Аня!
  print(greet(name: 'Дима', greeting: 'Хай'));  // Хай, Дима!

  print(tag('текст'));      // *текст*
  print(tag('текст', '_')); // _текст_

  // Функция как значение (первоклассные функции)
  Function op = add;
  print(op(10, 20)); // 30

  // Анонимная функция
  var triple = (int n) => n * 3;
  print(triple(5)); // 15

  // Замыкание — функция помнит окружение
  Function counter() {
    var count = 0;
    return () => ++count; // захватывает count
  }
  var next = counter();
  print(next()); // 1
  print(next()); // 2 (состояние сохранилось)
}

9. Классы и ООП

class Person {
  // Поля
  String name;
  int age;

  // Конструктор (краткая форма: this.x присваивает поля)
  Person(this.name, this.age);

  // Именованный конструктор
  Person.guest()
      : name = 'Гость',
        age = 0;

  // Метод
  void greet() {
    print('Привет, я $name, мне $age');
  }

  // Геттер — вычисляемое свойство (без скобок при вызове)
  bool get isAdult => age >= 18;

  // Сеттер — присваивание через =
  set rename(String newName) => name = newName;
}

void main() {
  // new не обязателен
  var p = Person('Аня', 25);
  p.greet();          // Привет, я Аня, мне 25
  print(p.isAdult);   // true (геттер без скобок)
  p.rename = 'Анна';  // вызов сеттера
  print(p.name);      // Анна

  var g = Person.guest(); // именованный конструктор
  print(g.name);          // Гость
}

10. Наследование и миксины

extends — наследование, abstract — абстрактный класс, implements — интерфейс, mixin — переиспользуемое поведение.

// Абстрактный класс — нельзя создать напрямую
abstract class Animal {
  String get name;
  void sound(); // абстрактный метод (без тела)
}

// extends — наследование, @override — переопределение
class Dog extends Animal {
  @override
  String get name => 'Пёс';

  @override
  void sound() => print('Гав');
}

// mixin — набор методов для подмешивания
mixin Swimmer {
  void swim() => print('Плыву');
}

// with — подмешать миксин
class Duck extends Animal with Swimmer {
  @override
  String get name => 'Утка';
  @override
  void sound() => print('Кря');
}

void main() {
  var d = Dog();
  d.sound();        // Гав
  print(d.name);    // Пёс

  var duck = Duck();
  duck.sound();     // Кря
  duck.swim();      // Плыву (из миксина)

  // super — обращение к родителю
  // (в Dog можно вызвать super.method() из переопределённого метода)
}

11. Асинхронность

Future — значение, которое появится позже. async/await — удобная работа с ним. Stream — поток значений во времени.

// Функция возвращает Future<String> — результат «в будущем»
Future<String> fetchUser() async {
  // await приостанавливает функцию до готовности Future
  await Future.delayed(Duration(seconds: 1)); // имитация задержки
  return 'Аня';
}

// Stream — последовательность значений (async*, yield)
Stream<int> countTo(int n) async* {
  for (var i = 1; i <= n; i++) {
    await Future.delayed(Duration(milliseconds: 100));
    yield i; // отдаём значение в поток
  }
}

// main тоже может быть async
Future<void> main() async {
  print('Загрузка...');
  var user = await fetchUser(); // ждём результат
  print('Привет, $user!');      // Привет, Аня!

  // await for — перебор значений Stream по мере поступления
  await for (var i in countTo(3)) {
    print('тик $i'); // тик 1, тик 2, тик 3
  }

  // Альтернатива: .then() вместо await
  fetchUser().then((u) => print('then: $u'));

  // Обработка ошибок
  try {
    await Future.error('сбой');
  } catch (e) {
    print('Поймал: $e'); // Поймал: сбой
  }
}

12. Дженерики

Параметризация типом <T> — один код для разных типов с сохранением типобезопасности.

// Обобщённая функция: T — параметр-тип
T firstOf<T>(List<T> items) => items.first;

// Обобщённый класс — простой стек
class Stack<T> {
  final List<T> _items = [];

  void push(T item) => _items.add(item);
  T pop() => _items.removeLast();
  bool get isEmpty => _items.isEmpty;
}

// Ограничение типа: T должен быть num (или его наследником)
T maxOf<T extends num>(T a, T b) => a > b ? a : b;

void main() {
  print(firstOf<int>([10, 20, 30])); // 10
  print(firstOf(['a', 'b']));        // a (тип выведен)

  var s = Stack<String>();
  s.push('a');
  s.push('b');
  print(s.pop());     // b
  print(s.isEmpty);   // false

  print(maxOf(3, 7));     // 7
  print(maxOf(1.5, 0.5)); // 1.5
}

Готово! Это ядро Dart. Дальше — records и pattern matching (Dart 3), пакеты с pub.dev и, конечно, Flutter для UI.

Поддержать проект