【入門編】Dartの共変性(Covariance)とNull安全:Listが引き起こす実行時エラーの回避術 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

Dartの「共変性」とNull安全の深い関係:なぜリストの操作でアプリがクラッシュするのか?

こんにちは。Dartの深淵へようこそ。
Dartを触り始めた皆さんが必ず一度は立ち止まる場所、それが「共変性(Covariance)」と「Null安全」の複雑なダンスです。

「`List` を `List` に代入しても動くのに、なぜ実行時にエラーが出るんだ?」
そう感じたことはありませんか?実はこれ、Dartの型システムの柔軟性と、実行時の安全性を両立させるための「ギリギリの設計」によるものなのです。

今日は、Dartのコアアーキテクトの視点から、この「落とし穴」を完全に掌握する方法を伝授します。ここをクリアすれば、あなたのDartコードの堅牢性は一段階上のレベルに到達しますよ。

—

1. 共変性(Covariance)の基本:親子関係の「方向」を理解する

まず、直感的な話をしましょう。
`int` は `num`(数値型)のサブタイプです。では、`List` は `List` のサブタイプと言えるでしょうか?

結論:Dartでは「Yes」です。これを共変(Covariant)と言います。

List intList = [1, 2, 3];
List numList = intList; // これはOK。intはnumの一種だから。
numList.add(4.5); // おっと!intListの中に小数が入ってしまった!

もしこのコードがコンパイルを通ってしまうと、`intList` を参照している別の場所で「整数が入っているはず」という前提が崩れ、実行時に `TypeError` が発生します。これが「共変性の罠」です。

—

2. Null安全と共変性の危険な化学反応

Null安全が導入されてから、この問題はよりシビアになりました。`List`(nullが入る可能性があるリスト)と `List`(nullは絶対にダメ)の関係です。

void main() {
List nullableList = [1, 2, null];

// 共変性により、List を List として扱えてしまう
List dynamicList = nullableList;

// ここで危険な操作!
dynamicList.add(“Dartの世界へようこそ”);

// 実行時エラー:List に String が混入した!
// Unhandled exception: type ‘String’ is not a subtype of type ‘int?’
print(nullableList[3]);
}

Dartのコンパイラは、`List` にキャストされた瞬間に「このリストの中身は何でもありだ」と判断し、型チェックのガードを下げてしまいます。しかし、Dart VMはメモリ上で「元々このリストは何を格納すべきだったか」を監視しています。

この監視こそが、Dartの「Sound Null Safety(健全なNull安全)」を支える強力な番人なのです。

—

3. 実行時エラーを未然に防ぐ「3つの鉄則」

現場でこのクラッシュを避けるには、以下の3つの防御策を脳内に叩き込んでください。

① `List` への暗黙的なキャストを避ける

可能な限り、具体的な型を指定しましょう。`dynamic` は型システムの「逃げ道」であり、同時に「爆弾」でもあります。

// 悪い例:なんでも受け取ってしまう
void processData(List data) { … }

// 良い例:ジェネリクスを活用する
void processData(List data) { … }

② `List.cast()` を使った安全な変換

既存のリストを別の型のリストとして扱いたい場合、`as` で強引に変換するのではなく、`.cast()` を使いましょう。これは変換時に中身をチェックする準備を整えてくれます。

List rawList = [1, 2, null];
// 明示的にキャストすることで、型安全性を維持する
List safeList = rawList.whereType().toList();

③ イミュータブル(不変)なリストを意識する

もしデータが変更される必要がないなら、`List` をむやみに公開せず、`Iterable` や `UnmodifiableListView` を使って「書き込み不可」の状態で渡すのが、アーキテクトとしての作法です。

import ‘dart:collection’;

List original = [1, 2, 3];
// 外部からは読み取り専用として提供する
final readOnlyList = UnmodifiableListView(original);

—

最後に:Dartの型システムと仲良くなるために

共変性は、コードの柔軟性を高めるために必要な機能です。しかし、自由には責任が伴います。

Dart VMは、皆さんが書いたコードをAOT(Ahead-of-Time)コンパイルし、メモリを極限まで最適化して実行します。その際、型情報がメモリの守り神として働いています。皆さんが「ここには絶対に `int` しか入らない」と型を厳格に指定すればするほど、Dartはより速く、より安全に動くのです。

「なぜエラーになるのか?」と悩んだときは、コンパイラが「あなたの宣言した型と、実際のメモリの内容が食い違っているよ!」と必死に警告してくれているのだと考えてみてください。

さあ、型安全を武器にして、より堅牢なFlutterアプリを構築していきましょう。皆さんのコードが、さらに洗練されることを楽しみにしています!

タイトルとURLをコピーしました