【入門編】Dartの型システムにおけるNever型の役割と、例外処理の網羅性チェック – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!Dartの奥深い世界へようこそ。
Flutterを使ったアプリ開発や、Dartでのサーバーサイド開発を楽しんでいますか?

今回は、Dartの型システムにおける隠れた主役であり、コンパイラを完全に手玉にとるための最強のカード「`Never`型」についてお話しします。

「他の言語ではあまり見かけないけれど、一体何をするものなの?」
「エラー処理や無限ループでどう役立つの?」

そんな疑問を抱えている方も多いはずです。ここをクリアすれば、Dartの型システムがあなたにとって「単なる面倒な制約」ではなく、「最高の相棒」に変わります。さあ、一緒に扉を開けていきましょう!

—

1. `Never`型とは何か?(コンパイラを唸らせる絶対の事実)

私たちが普段使う `int` や `String`、あるいは `bool` などの型は、「その変数や値が、どのようなデータを持っているか」を表しますよね。

では、`Never`型は何を表すでしょうか?
結論から言うと、`Never`型は「絶対に値が存在しない(正常に完了することがない)」という恐ろしいほど冷徹な事実を表します。

[正常なプログラムの世界]
int x = 10; // 値が存在する

[Neverの世界]
void doSomething() {
throw Exception(‘爆発しました’); // ここから先には絶対に到達しない!
}

DartのAOT(Ahead-Of-Time)コンパイラやJITコンパイラは、コードを解析する際、「このコードの実行結果はどうなるか?」を厳密に追跡しています。ここで関数が例外を投げたり、無限ループに入ったりして「絶対に呼び出し元に戻ってこない(正常終了しない)」場合、その戻り値の型として `Never` が使われます。

—

2. 基本的な使い方:関数が「絶対に終わらない」ことを伝える

まずは、もっともシンプルな例を見てみましょう。
プログラムを強制終了させるための専用関数を書くとき、`Never` が真価を発揮します。

// この関数は「絶対に正常終了しない」ため、戻り値の型を Never にします
Never crashAndBurn(String message) {
throw StateError(‘【致命的エラー】: $message’);
}

void processUser(String? name) {
// name が null だったらアプリを強制終了する
// この時、userName は必ず非nullの String であるとコンパイラに保証させられる
String userName = name ?? crashAndBurn(‘ユーザー名が取得できませんでした’);

// コンパイラは crashAndBurn が戻ってこないと知っているので、
// ここ以降の userName は確実に String であると安心して処理できる
print(‘ようこそ、${userName.toUpperCase()}さん!’);
}

ここがポイント!

もし `crashAndBurn` の戻り値が `void` だと、「関数は何も返さないけれど、呼び出し元には戻ってくるかもしれない」とコンパイラは解釈してしまいます。そのため、`name ?? …` の右側として不適切だと怒られてしまいます。

しかし、`Never` を使うことで、コンパイラはこう理解します。
> 「ほう、右側の関数は二度とここに戻ってこない(=処理がここで途切れる)のだな。ということは、左側が null の場合、この行の先へ進むことは物理的にあり得ない。つまり、`userName` は 100% 安全に `String` 型だ!」

このコンパイラとの「意思疎通」ができるようになると、Null安全のコードが劇的に書きやすくなります。

—

3. 実用例:例外処理の網羅性チェック(Exhaustiveness Checking)

`Never` 型のもう一つの美しい使い道が、パターンマッチングや `switch` 文における「網羅性(すべてカバーされているか)のチェック」です。

例えば、アプリの状態を表す `enum` があるとします。

enum AuthStatus { authenticated, unauthenticated, loading }

void handleAuth(AuthStatus status) {
switch (status) {
case AuthStatus.authenticated:
print(‘ログイン中’);
break;
case AuthStatus.unauthenticated:
print(‘未ログイン’);
break;
case AuthStatus.loading:
print(‘読み込み中’);
break;
}
}

ここで、将来的にチームメンバーが新しい状態 `AuthStatus.banned(アカウント凍結)` を `enum` に追加したとしましょう。
その時、上記の `handleAuth` 関数に新しい `case` を追加し忘れても、従来のDartではコンパイルエラーにならず、実行時まで気づきにくいという問題がありました。

これを `Never` 型を使って、「新しい状態を追加したら、コンパイルエラーで絶対に気づける仕組み」にアップデートしてみましょう。

enum AuthStatus { authenticated, unauthenticated, loading, banned }

void handleAuth(AuthStatus status) {
switch (status) {
case AuthStatus.authenticated:
print(‘ログイン中’);
break;
case AuthStatus.unauthenticated:
print(‘未ログイン’);
break;
case AuthStatus.loading:
print(‘読み込み中’);
break;

// まだ処理していないケースがある場合、ここに到達してしまう
case AuthStatus.banned:
// ここをあえて書く!
break;
}
}

さらに進んで、Dartの型チェッカーの性質を利用します。
Dartでは、すべてのコードパスを網羅した場合、最後に `default` や予期せぬ値に対する処理を書くことになります。ここで「これ以上値がないはず(=ここに到達するはずがない)」という変数に対して `Never` 型を割り当てます。

void handleAuthSmart(AuthStatus status) {
switch (status) {
case AuthStatus.authenticated:
print(‘ログイン中’);
break;
case AuthStatus.unauthenticated:
print(‘未ログイン’);
break;
case AuthStatus.loading:
print(‘読み込み中’);
break;
// ‘banned’ が追加されたのに、ここでケースを書き忘れたとする…
}

// ここで status を Never 型の変数に代入しようとしてみる
// すべての enum 値が上の switch で処理されていれば、status は Never になるはず。
// しかし、banned が未処理の場合、status はまだ banned の可能性を残しているため、
// 「AuthStatus を Never 型に代入できません」というコンパイルエラーが発生する!

// ネタばらし:最新のDart(Dart 3以降)ではパターンマッチングの Exhaustiveness checking が
// 言語機能として組み込まれていますが、内部の理論はまさにこの Never 型の概念に基づいています!
}

もしすべてのケースが網羅されていれば、switchを抜けた後の `status` は「あり得ない状態(=`Never`)」に収束します。もし網羅漏れがあればコンパイルが止まる。これが、大規模開発を破綻させないためのプロフェッショナルの技です。

—

4. 陥りやすい文法エラーと注意点

初学者の開発現場でよく見られるミスをいくつかご紹介します。これらを避けるだけで、コードの質がグッと上がります。

① `null` と `Never` を混同する

  • `null`: 「値が存在するが、それが空(から)である」という状態。型で書くと `Null` 型(または `void`)。
  • `Never`: 「値が存在すらしない(コードの実行がそこに到達しない)」。

`Never` 型の変数に何かを代入することはできません(`null` すら代入不可能です)。

Never n = null; // 【コンパイルエラー!】 Null を Never に代入することはできません

② 戻り値の書き忘れと間違える

関数で `return` を書き忘れた場合、Dartは自動的に `void`(厳密には `null`)を返そうとします。それを無理やり `Never` にすると、コンパイラから「この関数、普通に戻ってきてるやん!」と突っ込まれます。

// ❌ 誤った書き方
Never doSomethingWrong() {
print(‘ここに到達して戻ってくる’);
// return がない(あるいは単に処理が終わる)のでエラーになる!
}

必ず `throw` するか、無限ループ(`while (true) {}`)にするなどして、絶対にコードの実行が関数の外へ抜け出さないようにしてください。

—

まとめ:ここをクリアすれば、Dartの基本はバッチリマスターできます!

今回は、Dartの型システムの底支えをする `Never` 型について解説しました。

  • `Never` 型は、「絶対に正常終了しない・値が存在しない」ことをコンパイラに伝える型。
  • 例外を投げるヘルパー関数や、プログラムの強制終了処理で大活躍する。
  • コンパイラとの型推論の連携を強め、Null安全なコードを美しく書くために欠かせない。

ここまで理解できれば、あなたのDartの型システムに対する解像度は、一般的なプログラミング学習者を大きく引き離しているはずです。コンパイラの挙動を味方につけ、安全で堅牢なコードを自信を持って書いていきましょう!

それでは、次のレクチャーでお会いしましょう。バッチリマスターしていってくださいね!

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