Ответ
Null safety, стабильно представленная в Dart 2.12, — это одно из самых значимых обновлений языка. Она переносит обнаружение ошибок, связанных с null, со времени выполнения (runtime) на время компиляции. Вот как я её использую:
-
Non-nullable по умолчанию
- Все типы по умолчанию не могут содержать
null. Это кардинально меняет ментальную модель и устраняет целый класс ошибок.String name = 'Flutter'; // OK String name = null; // Ошибка компиляции: значение не может быть null.
- Все типы по умолчанию не могут содержать
-
Nullable типы (с суффиксом
?)- Чтобы переменная могла быть
null, это нужно указать явно.String? nickname = null; // OK, может быть null nickname = 'Bob'; // OK print(nickname.length); // Ошибка компиляции: переменная может быть null.
- Чтобы переменная могла быть
-
Операторы для безопасной работы с nullable-переменными
- Оператор безопасного доступа (
?.)int? length = nickname?.length; // length будет типа int? (null, если nickname null) - Оператор объединения с null (
??)String greeting = 'Hello, ' + (nickname ?? 'Guest'); // Использует 'Guest', если nickname null - Оператор утверждения, что не-null (
!)// Используется, когда вы логически уверены, что значение не null. // Если оно всё же null, выбросится runtime-исключение. print(nickname!.length);
- Оператор безопасного доступа (
-
Ключевое слово
requiredдля named-параметров- Заменяет старый
@requiredаннотацию и делает проверку обязательности на уровне языка.void createUser({required String name, required int age}) { ... } // createUser(age: 25); // Ошибка компиляции: параметр 'name' обязателен.
- Заменяет старый
-
Поздняя инициализация (
late)-
Позволяет объявить non-null переменную, инициализация которой будет гарантированно произведена до первого использования, но не сразу.
class ApiService { late final Dio _client; // Не-null, но инициализируется позже ApiService(String baseUrl) { _client = Dio(BaseOptions(baseUrl: baseUrl)); // Инициализация в конструкторе } } // Использование _client до инициализации вызовет LateInitializationError.
-
Эта система заставляет явно обрабатывать случаи с null, что делает код значительно более предсказуемым и надежным, и это чувствуется в каждом Flutter-проекте.
Ответ 18+ 🔞
Слушай, а вот эта ваша null safety в Dart — это вообще, блядь, огонь, ядрёна вошь! Раньше-то как было? Ловишь NullPointerException в рантайме, как дурак, а потом полдня дебажишь, кто тебе null в переменную сунул. А теперь, ёпта, компилятор сам тебя за руку ловит и орёт: "Мужик, ты куда лезешь, там же null может быть, ёб твою мать!".
Вот смотри, как теперь жизнь изменилась:
-
Теперь всё по-честному, не-nullable по умолчанию. Раньше каждая переменная была как хитрая жопа — в любой момент могла оказаться пустой. А теперь если ты написал
String, то это строка, а не манда с ушами. Попробуй запихнуть тудаnull— получишь по шапке сразу, на этапе компиляции.String name = 'Flutter'; // Всё чики-пуки String name = null; // Компилятор: "Какого хуя? Сам от себя охуел? Я тебе разрешал?" -
Хочешь по-старому, жить опасно? Пожалуйста, но явно пометь (
?). Сказал, что переменнаяString?— ну окей, значит, можешь быть героем и хранить тамnull. Но теперь компилятор тебе не даст просто так взять и обратиться к её свойствам. Защита от дурака, блядь.String? nickname = null; // Ну ладно, храни свою пустоту. print(nickname.length); // А вот тут компьютор тебя останавливает: "Чувак, а если там null? Ты уверен? Доверия ебать ноль". -
Инструменты для работы с этой nullable-хренью.
- Безопасный доступ (
?.) — типа, "если не null, то сделай, а если null, то да похуй, верни null и иди дальше". Удобно, не надо городитьif.int? length = nickname?.length; // length либо число, либо тоже null. Чётко. - Костыль на случай пиздеца (
??) — "если слева хуйня (null), то бери то, что справа". Спасение для дефолтных значений.String greeting = 'Hello, ' + (nickname ?? 'Guest'); // Если nickname пустой, гость будет. Красота! - Взрывная кнопка (
!) — "Я, блядь, на 146% уверен, что тут не null! Если я ошибаюсь — пусть всё ебётся конём!". Рискованно, но иногда надо.print(nickname!.length); // Программист клянётся, что nickname не null. Если соврал — runtime исключение, всем пи*дец.
- Безопасный доступ (
-
required— это теперь не просто аннотация, а закон. Раньше@requiredможно было проигнорировать. Теперь если в параметрах функции стоитrequired, то ты обязан передать значение. Не передашь — компилятор тебя не пропустит, будет орать, как резаный. Порядок наводит.void createUser({required String name, required int age}) { ... } // createUser(age: 25); // Ошибка компиляции: "Мужик, а где name? required же, ёбаный насос!" -
Ленивая инициализация (
late) — для умных. Бывает, переменную non-nullable объявить надо сразу, а инициализировать — позже, в конструкторе или вinitState. Раньше приходилось пихать тудаnullи делать вид, что так и надо. Теперь говоришьlate— и всё, типа "я ручаюсь, что к моменту использования она будет готова". Обманешь — получишьLateInitializationErrorи будешь сам виноват.class ApiService { late final Dio _client; // Не-null, но повиси пока в ожидании. ApiService(String baseUrl) { _client = Dio(BaseOptions(baseUrl: baseUrl)); // А вот теперь инициализирую, всё честно. } } // Если попробовать использовать _client до этой строчки — будет тебе хиросима, сам напросился.
Короче, null safety — это пизда рулю. Сначала, конечно, волнение ебать, привыкать надо, кажется, что везде надо ? ставить. Но потом, когда проект растёт, а ошибок "Cannot read property of null" — ноль ебать, понимаешь, что это гениально. Код становится предсказуемым, как удар вилкой в глаз.