Dartの「共変性」という罠:Null安全を無力化するListの境界線と、その正しい解法
DartのSound Null Safetyは、コンパイル時にメモリ上の型整合性を保証する強力な武器だ。しかし、実務において`List
今日は、Dart VMがこの「型安全の穴」をどう処理しているか、そしてなぜあなたのコードが実行時に`TypeError`を吐くのか、その深淵を紐解こう。
—
1. なぜ「共変性」がNull安全をすり抜けるのか
Dartの`List`や`Iterable`は共変的(Covariant)である。これは、`List
危険なコードの正体
void main() {
List
// 共変性により、List
// ここでコンパイルは通るが、実行時にVMが「待った」をかける
rawList.add(null);
// 結果: Uncaught Error: type ‘Null’ is not a subtype of type ‘String’ of ‘value’
print(names.first);
}
この挙動こそが、Dartが実行時に型チェックを怠っていない証拠だ。しかし、これではNull安全の恩恵が台無しだ。コンパイル時のチェックだけでは防げない「代入の不整合」が、実行時のパフォーマンスを削り、予期せぬクラッシュを招く。
—
2. 現場で使える「型安全ラッパー」の設計
API連携や動的なJSONパースを行う際、`List
最もエレガントかつ高パフォーマンスな手法は、「検証付き変換(Validated Transformation)」を導入することだ。
実践:型安全なリストアダプター
以下は、外部から来た`List
class ListValidator
final List
// コンストラクタで型を強制する
ListValidator(List
: _items = rawData.whereType
List
// 外部からの混入を許さない設計
void add(T item) => _items.add(item);
}
void main() {
final dynamic apiResponse = [‘A’, null, ‘B’, 123];
// 不正なデータを即座にフィルタリングし、型安全な領域へ引き込む
final safeList = ListValidator
print(safeList.value); // [A, B] : nullやintは除外される
}
なぜこの設計が最強なのか
1. `whereType
2. 不変性(Immutability)の担保: `List.unmodifiable`を返すことで、外部からリストの内部構造をいじらせない。これがコンポーネント設計における「状態の単一方向性」を強化する。
—
3. パフォーマンスへの洞察:AOTコンパイル時の影響
DartのAOT(Ahead-of-Time)コンパイルにおいて、実行時の型チェックは「隠れたコスト」だ。共変性に依存したコードを多用すると、VMは常に実行時型チェック(Type Guard)を挿入せざるを得ない。
- 避けるべき: `dynamic`を用いた頻繁なリスト操作。これはVMが型推論を諦め、動的なディスパッチを行うため、CPUサイクルを無駄にする。
- 推奨: 境界(API受信時、またはDB読み込み時)で一度だけ型変換を行い、アプリケーションのコアロジックでは常にジェネリクスを明示した`List
`を使用する。
—
チーフアーキテクトからの助言
「コンパイルさえ通ればいい」という考えは、DartにおけるNull安全の哲学を理解していない証拠だ。
Null安全とは、「データの境界線をどこに引くか」という設計思想そのものである。フロントエンド開発において、JSONという「型のない混沌」を、いかに迅速に「静的な秩序」へ変換するか。その境界線で共変性を逆手に取り、堅牢なアダプター層を構築することこそが、プロダクション環境でバグをゼロに近づける唯一の道だ。
もしあなたがコードレビューで、`List
「型は、書くものではなく、設計するものだ。」
この知見を胸に、今日も美しいコードを書いてほしい。