Null safety begins in the static type system because everything else rests upon that. Before null safety, the static type system allowed the value null to flow into expressions of any of those types.
Null Safety has two types Nullable and Non-nullable:- Null safety eliminates that problem at the root by changing the type hierarchy. Null is no longer subtype as all types are non-nullable by default.
// Using null safety:
void makeCoffee(String coffee, [String? dairy]) {
if (dairy != null) {
print('$coffee with $dairy');
} else {c
print('Black $coffee');
}
}
void main() {
makeCoffee('Espresso', 'milk');
}
Output:-
Espresso with milk
// Hypothetical unsound null safety:
void bad(String? maybeString) {
print(maybeString.length);
}
void main() {
bad(null);
}
This will crash if we run it. You can use nullable types as map keys, store them in sets, compare them to other values, and use them in string interpolation.
It’s always safe to pass a non-nullable type to something expecting a nullable type. You can also safely pass null to something expecting a nullable type, so Null is also a subtype of every nullable type:
// Hypothetical unsound null safety:
void requireStringNotNull(String definitelyString) {
print(definitelyString.length);
}
void main() {
String? maybeString = null; // Or not!
requireStringNotNull(maybeString);
}
This program is not safe and we shouldn’t allow it. However, Dart has always had this thing called implicit downcasts. If you, for example, pass a value of type Object to a function expecting a String, the type checker allows it:
// Without null safety:
void requireStringNotObject(String definitelyString) {
print(definitelyString.length);
}
void main() {
Object maybeString = 'it is';
requireStringNotObject(maybeString);
}