Повышение нулевого типа безопасности при назначении литерала ненулевого значения
В https://nullsafety.dartpad.dev/, если я напишу следующий код:
void main() {
String? name = 'Bob';
print(name.length);
}
Я получаю следующую ошибку времени компиляции:
Выражение, значение которого может быть 'null', должно быть проверено на null, прежде чем его можно будет разыменовать.
И следующая ошибка времени выполнения:
Свойство length недоступно для String? потому что он потенциально равен нулю.
В документации по продвижению типа по нулевым проверкам говорится:
Язык также лучше понимает, какие выражения вызывают продвижение. Явный
== null
или!= null
конечно работает. Но явные приведения с использованиемas
, или присвоения, или постфикс!
Оператор, которого мы скоро доберемся, также вызовет продвижение. Общая цель состоит в том, что если код является динамически правильным и разумно вычислить это статически, анализ должен быть достаточно умным, чтобы сделать это.
Вопрос
Нет возможности name
может иметь значение null в приведенном выше коде. В документации также говорится, что присвоения должны вызывать повышение типа. Я неправильно понимаю продвижение шрифтов или это ошибка в DartPad?
Разъяснение
Поскольку пара ответов предоставляет обходные решения для сообщений об ошибках, я должен пояснить, что я не пытаюсь решить проблему с кодированием, указанную выше. Скорее, я говорю, что считаю, что код должен работать так, как он есть. Но это не так. Почему бы нет?
4 ответа
Этот ответ является ответом на награду, добавленную к исходному вопросу. Награда гласит:
Объясните, пожалуйста, как
String?
отличается отString
и как продвижение шрифтов работает в Dart.
Строка? vs String
Тип
String?
может содержать строку или
null
. Вот некоторые примеры:
String? string1 = 'Hello world';
String? string2 = 'I ❤️ Dart';
String? string3 = '';
String? string4 = null;
Тип String, с другой стороны, может содержать только строки (если нулевая безопасность является частью Dart). Он не может содержать
null
. Вот некоторые примеры:
String string1 = 'Hello world';
String string2 = 'I ❤️ Dart';
String string3 = '';
Если вы попытаетесь сделать следующее:
String string4 = null;
Вы получите ошибку времени компиляции:
A value of type 'Null' can't be assigned to a variable of type 'String'.
В
String
типа не может быть
null
больше, чем это могло быть
int
нравиться
3
или
bool
нравиться
true
. Это и есть нулевая безопасность. Если у вас есть переменная, тип которой
String
, вам гарантировано, что переменная никогда не будет
null
.
Как работает продвижение шрифта
Если компилятор может логически определить, что тип, допускающий значение NULL (например,
String?
) никогда не будет
null
, затем он преобразует (или продвигает) тип в его аналог, не допускающий значения NULL (например,
String
).
Вот пример, где это правда:
void printNameLength(String? name) {
if (name == null) {
return;
}
print(name.length);
}
Хотя параметр
name
допускает значение NULL, если на самом деле
null
тогда функция возвращается раньше времени. К тому времени, когда вы доберетесь до
name.length
, компилятор наверняка знает, что
name
не может быть
null
. Таким образом, компилятор продвигает имя из
String?
к
String
. Выражение
name.length
никогда не вызовет сбоя.
Похожий пример здесь:
String? name;
name = 'Bob';
print(name.length);
Хотя
name
здесь тоже можно обнулить, строковый литерал
'Bob'
явно не равно нулю. Это также вызывает
name
быть повышенным до не допускающего значения NULL
String
.
Первоначальный вопрос касался следующего:
String? name = 'Bob';
print(name.length);
Похоже, это также должно продвигать имя к не обнуляемому
String
, но этого не произошло. Однако, как отметил в комментариях@lrn (инженер Google), это ошибка, и когда обнаружится нулевая безопасность, это также будет работать, как и в предыдущем примере. То есть,
name
будет повышен до не допускающего значения NULL
String
.
дальнейшее чтение
Ошибка:
class Foo {
int? i = 0;
double d() => i.toDouble(); // Error
}
Ошибка, которую вы видите в таком коде, связана с тем, что геттеры не повышаются до своих аналогов, не допускающих значения NULL . Давайте поговорим о причине.
Причина:
Скажем, есть класс
Bar
который расширяет
Foo
и переопределить
i
переменная и не присваивает ей никакого значения (сохраняя ее):
class Bar extends Foo {
@override
int? i;
}
Итак, если бы вы могли сделать
print(Bar().d() * 2);
Вы бы столкнулись с ошибкой null во время выполнения, поэтому продвижение типа геттеров запрещено.
Решения:
Используйте локальную переменную для продвижения типа (рекомендуется)
class Foo { int? i = 0; double d() { var i = this.i; // Local variable in use. if (i != null) return i.toDouble(); return -1.0; // Some default value. } }
Использовать
?.
с участием??
class Foo { int? i = 0; double d() => i?.toDouble() ?? -1.0; // Provide some default value. }
Использовать
!
оператор взрыва, только если вы уверены, что значение никогда не будетnull
:class Foo { int? i = 0; double d() => i!.toDouble(); // Bang operator in use. }
Я понимаю, что вы говорите. Попробуйте это.
Чтобы продвижение типа сработало, вы должны сначала подтвердить, что значение не равно нулю, как указано в документации.
Как вы можете видеть на картинке, dart может продвигать тип или понимать, что имя не будет нулевым, потому что он заранее проверяет это в операторе if.
Но если использовать его вне оператора if без предварительной проверки, не является ли он значением null, dart знает, что ему можно снова присвоить значение null. Вот почему он рекомендует всегда проверять, является ли оно нулевым. Потому что любой установленной переменной (переменной с присвоенным значением) в будущем может быть присвоено значение null.
Поскольку вы объявили переменную name
как String, который может быть нулевым в какой-то момент вашего кода, когда вы его используете, вы должны проверить, является ли он нулевым, и безопасно вызвать его. Вы можете использовать свою переменнуюname
безопасно двумя способами:
1. ДОБАВЛЕНИЕ (?) К переменной
Если вы уверены, что переменная не равна нулю, вы можете использовать / вызвать ее, просто добавив ? в теме.
void main() {
String? name = 'Bob';
print(name?.length);
}
2. ДОБАВЛЕНИЕ (!) К переменной
Добавляем ! к переменнойname
вызовет ошибку времени выполнения, если он имеет значение NULL, и в противном случае преобразует его в значение, не допускающее значения NULL.
void main() {
String? name = 'Bob';
print(name!.length);
}
Чтобы избежать ошибки выполнения, если переменная name
имеет значение null, вы должны сначала проверить его допустимость, а затем безопасно использовать.
void main() {
String? name = 'Bob';
if (name != null) {
print(name!.length);
}
}