こんにちは。Dartの深淵へようこそ。
あなたが日々何気なく書いている `String? name` というコード。これに「Null安全(Sound Null Safety)」という名前がついているのは単なるお化粧ではありません。これは、Dartのコンパイラがあなたのコードを数学的・論理的に証明し、「実行時に絶対にNullPointerExceptionを起こさせない」という強力な契約を結んでいるのです。
今日は、Dartコンパイラが舞台裏でどのようにあなたのコードを「監視」し、型を昇格させているのか、そのアルゴリズムの核心に迫りましょう。
—
1. コンパイラは「探偵」である:フロー解析の正体
Dartのコンパイラは、コードを上から順に読みながら、変数の状態を常に記録しています。これを「フロー解析(Flow Analysis)」と呼びます。
例えば、こんなコードがあったとします。
void greet(String? name) {
// ここでは name は String? (Null許容)
if (name != null) {
// コンパイラ:「直前の if で null を排除したな。よし、このブロック内では name を String として扱うぞ」
print(name.length); // 昇格(Promotion)成功!
}
// ここではまた String? に戻る
}
コンパイラは「ここを通れば必ずnullではない」というパスを見つけた瞬間、型を `String?` から `String` へと「型昇格(Type Promotion)」させます。これは魔法ではなく、コンパイラが抽象構文木(AST)を辿りながら、変数の有効範囲(スコープ)と代入履歴を厳密に追跡している結果なのです。
—
2. なぜ「ローカル変数」だけなのか?
ここが初心者の方が最初につまずくポイントです。Dartの型昇格は、基本的に「ローカル変数」に対してのみ機能します。
class User {
String? name;
void printName() {
if (name != null) {
// エラー:’name’ はクラスのフィールド(プロパティ)なので昇格できない
// print(name.length);
}
}
}
「なぜ?」と思いますよね。理由はシンプルで、「クラスのプロパティは他のIsolateやメソッドから書き換えられる可能性があるから」です。
あなたが `if (name != null)` とチェックした次の瞬間に、別のスレッドや別の関数が `name = null` と書き換えるかもしれません。コンパイラは「今この瞬間はnullじゃない」ことを証明できても、「この先の未来もずっとnullじゃない」ことを証明できないため、安全側に倒して昇格を許可しないのです。
解決策: 一度ローカル変数にコピーしましょう。
void printName() {
final name = this.name; // ローカル変数に退避
if (name != null) {
print(name.length); // これならOK! ローカル変数は誰にも干渉されないから
}
}
—
3. フロー解析を味方につける:陥りやすい罠
フロー解析は賢いですが、時として人間の直感とズレることがあります。
罠1:関数の呼び出しを跨いだ昇格はできない
コンパイラは「その関数の内部」で完結する論理しか追えません。外部の関数が変数を書き換える可能性があるため、追跡を諦めます。
罠2:複雑な条件分岐
String? name = “Dart”;
if (name != null && name.length > 0) {
// ここは通る
}
これは「短絡評価(&&)」をコンパイラが理解しているため昇格します。しかし、複雑な自作のチェック関数を挟むと、コンパイラは「その関数が何をしているか」を解析しきれず、昇格を諦めることがあります。
—
4. コンパイラと仲良くする極意
DartのNull安全をマスターするコツは、「コンパイラの視点に立つこと」です。
1. 「これは誰にも変更されない(ローカル変数)」とコンパイラに確信させる。
2. `if (x != null)` という明確なガード句を置く。
3. どうしても昇格できない時は、`!`(強制アンラップ)ではなく、上記のようにローカル変数へ退避する設計にする。
最後に:Dartの哲学
Dartの型システムは、単に「エラーを出すためのもの」ではありません。「開発者が書いた論理を、実行前に数学的に保証するもの」です。
もしコンパイラに怒られたら、それは「あなたのコードに論理の穴があるよ」と、世界最高峰のエンジニア(Dartコンパイラ)が親切に教えてくれているサインです。その穴を埋める作業こそが、堅牢なアプリケーションを作るための最短ルートなのです。
ここを理解できれば、もうあなたの書くコードは「動く」だけでなく「壊れない」コードへと進化しています。自信を持って、どんどん書いていきましょう!