【入門編】Null安全における『共変性(Covariance)』の罠:ListとListの代入可能性 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは。Dartの深淵へようこそ。

Dartの型システムは、一見すると直感的で親しみやすいものですが、その裏側には堅牢な「Sound Null Safety(健全なNull安全)」という強力な守護者が存在しています。

今日は、多くの開発者が一度は頭を抱える「共変性(Covariance)」と「リストの代入可能性」について、コンパイラが裏で何を考えているのかを紐解いていきましょう。ここを理解すれば、Dartの型安全性が「ただのルール」ではなく「実行時のバグを未然に防ぐ防壁」であることが腑に落ちるはずです。

—

1. なぜ `List` に `List` を代入できないのか?

まず、直感に反するこのコードを見てください。

List nullableList = [1, null, ‘dart’];
// List nonNullableList = nullableList; // ❌ コンパイルエラー!

「`Object?` は `Object` の親分(スーパータイプ)なのだから、代入できていいじゃないか」と思いますよね。しかし、Dartのコンパイラはこれを許しません。なぜか?

それは、「後から `null` が混入するリスク」を排除するためです。

思考実験:もし代入できたら何が起きるか

もし仮に代入できてしまった場合、以下の恐ろしい事態が発生します。

List nullableList = [1, 2, 3];
List nonNullableList = nullableList; // もしこれが許されたら…

nonNullableList.add(null); // 😱 大惨事!
// nonNullableListは「Object(nullは入らない)」と約束したはずなのに、
// 参照先であるnullableList経由でnullが紛れ込んでしまう。

`nonNullableList` を利用するコード側は、「ここには絶対 `null` が入っていない」と信じて処理を書きます。もしそこに `null` が混入すれば、実行時に `NullPointerException`(Dartでは `NoSuchMethodError`)が発生し、アプリはクラッシュします。

Dartは「推論の嘘」を許さない言語です。だからこそ、この代入をコンパイル時に断固拒否するのです。

—

2. 共変性(Covariance)の基本:それは「型が広がる方向」

共変性とは、簡単に言えば「型が継承関係に従ってそのままの方向に変換できる性質」のことです。