![見出し画像](https://assets.st-note.com/production/uploads/images/49458276/rectangle_large_type_2_90a48ba68805111a757c3e76f870d224.png?width=800)
DartのNullSafetyについて確認してみる1
Flutter2からNullSafetyに対応したDartが使えるようになった。そこで、NullSafetyに対応したことによって、何が出来るようになったのかを確認しておこうと思う。
NullSafetyとは
nullになる可能性があるかどうかを表現できる仕組み。
NullSafetyに対応していない時は `int hoge` と宣言した場合 hoge には null が入ってくる可能性がある。しかし、NullSafetyに対応している時は `int hoge` と宣言した場合は null が入ってこない、という扱いにできる。逆に、 `int? hoge` と宣言すると null が入ってくる場合がある、という扱いにできる。
int hoge = 1; // nullは代入できない
int? hoge = null; // nullを代入できる
で、NullSafetyに対応したことで何が嬉しいのかというと、意図せずnullが入ってきてNoSuchMethodError等になってしまう状況を防ぎやすくなること。
つまり、nullになること自体が悪なのではなく、nullになる可能性があるか分からない状態が悪であり、それの1つの解決策として nullになる可能性があるかどうかを表現できるようにした。
Nullability in the type system
まず、List や int など各タイプにはメソッドやプロパティが定義されているが、nullにはそういった物が定義されていない。なので、null に対してメソッドを呼んだりしてしまうとエラーになってしまう。
で、なぜそうなっていたかというと、Nullが各タイプのサブタイプになっていたのが理由らしい。そこで、NullSafety対応後はNullだけ特別扱いし、独立したタイプとして切り離した。
また、切り離されたNullには toString(), ==, hasCode が定義されているので、それらは使える。`null.toString()` はOKである。
NullSafetyに対応した後はキャストのされ方も少し異なるらしい。対応前は暗黙的にダウンキャストされていたが、そういった仕組みは排除された。
Object a = 'hoge';
String b = a; // NullSafety対応前はOK、対応後はエラーとなる
String c = a as String; // 明示的にキャストすれば対応後もOK
Ensuring correctness
関数内で何もreturnしなかった場合はnullを返していた。なので、NullSafety対応後は↓の様な関数はエラーとなる。
String helloWorld() {
// 返り値は String を期待しているので、何もreturnしないのはエラーとなる
}
Non−Nullableな変数の初期化処理は、定義する場所よって少し扱いが変わるらしい。
・グローバル変数・static変数は初期化が必要
・ローカル変数は初期化が不要
int a = 1; // 初期化が必要
class Fuga {
static int b = 2; // 初期化が必要
}
void hoge(int n) {
String s; // 初期化しなくても良い
if (n == 1) {
s = 'HELLO WORLD';
print(s); // これは s に値が入るのでOK
} else {
print(s); // s に値が入らないのでエラーとなる
}
}
Flow analysis
記述されたコードの解釈も変わっているらしい。
まず、if文でタイプの判定を行った時、素直に書いた場合は判定されたタイプとして扱うことができる。これは、NullSafety対応前から可能。
bool isEmptyList(Object object) {
if (object is List) {
return object.isEmpty; // <-- OK!
} else {
return false;
}
}
しかし、if (object is! List) return false; の様に、タイプ判定を行いつつ早期リターンした場合はタイプをいい感じに解釈してくれなかった。NullSafety対応後はそれが改善され、いい感じに解釈してくれるらしい。
bool isEmptyList(Object object) {
if (object is! List) return false;
return object.isEmpty; // NullSafety対応後は動く
}
Nullに関しても同様で、== null や != null をいい感じに解釈してくれるようになっている。
String hoge(String? message) {
if (message == null) {
return 'NULL';
}
return message.trim(); // OK
}
String fuga(String? message) {
if (message != null) {
return message.trim(); // OK
}
return 'NULL';
}
次に、到達不能コードにはNeverを使うらしい。NeverはNullSafety対応後に登場したタイプで、全てのタイプのサブタイプだと。
具体的な使い方はこんな感じ。throw自体がNeverを表現しているのでこうなるらしい。
Never wrongType(String type, Object value) {
throw ArgumentError('Expected $type, but was ${value.runtimeType}.');
}
class Point {
final double x, y;
bool operator ==(Object other) {
if (other is! Point) wrongType('Point', other);
return x == other.x && y == other.y;
}
// Constructor and hashCode...
}
次に、変数の初期化に関してもいい感じに解釈してくれるらしい。NullSafety対応前は final String message; のように、宣言と同時に初期化してないとエラーとなっていたが、NullSafety対応後は後から必ず初期化されるのであればOKとなっている。
String hoge(int n) {
final String message;
if (n == 1) {
message = 'one';
} else {
message = 'etc';
}
return message;
}
最後に
Nullになる可能性があるかどうかを表現できる程度かと思っていたが、新しいタイプが登場したり、コードの解釈も改善されていた。地味ではあるがこういう所を理解しておくとより安全なコードを、より理解しやすい形で書けるようになるかもしれない。
後半は次回の投稿で読み込んで行きたいと思う。
この記事が気に入ったらサポートをしてみませんか?