Переменные, типы и null-safety
В Dart значение «ничего» (null) нельзя получить случайно — компилятор стоит на страже.
Суть: переменные объявляют через
var,finalилиconst. У каждого значения есть тип, а главная защита языка — null-safety: переменная не может быть null, пока вы явно не разрешите это знаком?.
Чтобы прочувствовать ценность null-safety, вспомните, сколько приложений вылетало у вас на телефоне с внезапным закрытием. Огромная доля таких падений в других экосистемах — это именно обращение к значению, которого не оказалось. Dart переносит эту проблему с этапа работы приложения на этап написания кода: вы видите потенциальную пустоту прямо в редакторе, подчёркнутую красным, и чините её, пока пользователь ещё ничего не заметил. Это не формальность, а реальное снижение числа сбоев в продакшене.
Переменная — это именованная коробка для значения. В Dart есть три способа создать такую коробку, и разница между ними решает половину будущих багов. var — обычная переменная, её значение можно поменять. final — значение присваивается один раз и больше не меняется (но вычисляется во время работы программы). const — значение известно ещё на этапе компиляции и зашито намертво. На практике опытные разработчики по умолчанию пишут final и переходят на var только когда переменную действительно нужно менять.
var age = 20; // можно менять: age = 21;
final name = 'Аня'; // присвоили один раз
const pi = 3.14159; // константа на этапе компиляции
age = 21; // ОК
// name = 'Боря'; // ОШИБКА: final нельзя переприсвоить
Базовые типы Dart: int (целые), double (дробные), String (текст), bool (истина/ложь). Тип можно писать явно (int age = 20;), но чаще его выводит сам компилятор из значения справа — это называется выведение типов.
Как работает null-safety под капотом
В большинстве языков любая переменная может внезапно оказаться null («пусто»), и обращение к ней роняет программу с печально известной ошибкой. Dart 3 решает это sound null-safety: по умолчанию String name никогда не может быть null. Если вы хотите допустить пустоту — добавьте ? к типу: String? name. Тогда компилятор заставит вас проверять значение перед использованием.
String name --> гарантированно есть значение
String? name --> может быть значением ИЛИ null
|
обращение v
name!.length --> "я уверен, что не null" (рискованно)
name?.length --> безопасно: вернёт null, если name == null
name ?? 'нет' --> подставить запасное значение
Три оператора решают всё. ?. — безопасный доступ: если слева null, вся цепочка вернёт null вместо краха. ?? — значение по умолчанию: «возьми левое, а если оно null — правое». ! — оператор «я точно знаю, что тут не null»; он снимает защиту, и если вы ошиблись, программа упадёт. Поэтому ! используют редко и осознанно.
Ниже — запускаемая модель той же логики на Python: подстановка значения по умолчанию, когда данных нет. Это ровно то, что делает оператор ?? в Dart.
# Аналог Dart-оператора ?? — запасное значение, если данных нет
def display_name(name):
# name ?? 'Гость' на Dart
return name if name is not None else 'Гость'
print(display_name('Аня')) # Аня
print(display_name(None)) # Гость
# Аналог ?. — безопасный доступ к длине
def safe_length(text):
return None if text is None else len(text)
print(safe_length('Flutter')) # 7
print(safe_length(None)) # None
Частые ошибки
- Лепить
!везде, чтобы заткнуть ошибки компилятора. Это снимает защиту и возвращает старую боль с падениями. Лучше проверьте значение. - Путать
finalиconst.constтребует значения, известного на этапе компиляции;finalдопускает вычисление во время работы. - Объявлять
String? xбез необходимости. Если значение всегда есть, делайте тип ненулевым — меньше проверок в коде.
Best practices
- По умолчанию пишите
final;var— только для реально меняющихся переменных. - Избегайте
!. Заменяйте его на?.,??или явную проверкуif (x != null). - Давайте переменным понятные имена в стиле lowerCamelCase:
userName, а неun.
Полезно держать в голове и третий оператор — ??=, который присваивает значение только если переменная сейчас равна null. Он пригодится для ленивой инициализации: cache ??= computeExpensive() посчитает дорогое значение лишь однажды. Вместе с ?., ?? и продуманным выбором между ненулевым и нулевым типом эти инструменты дают полный контроль над пустотой, и привычка ими пользоваться отличает аккуратный Dart-код от случайного.
Итог: используйте final по умолчанию, доверяйте выведению типов и относитесь к null-safety как к другу: знак ? в типе — это явное «здесь может быть пусто», а операторы ?. и ?? позволяют работать с пустотой безопасно.