こんにちは!FlutterやDartを使った開発を楽しんでいますか?
今回は、Dartの型システムの中でも、一歩進んだ「知る人ぞ知る強力な武器」である `Never` 型 についてお話ししますね。
「エラーを投げるだけの関数を作ったら、コンパイラから『戻り値の型が違うよ』って怒られてしまった……」
そんな経験はありませんか?他の言語からDartに入った方だと、特に戸惑うポイントかもしれません。
ここをクリアすれば、Dartの型システムが裏側でどうあなたを支えているのか、その本質がグッと見えてきますよ。さあ、一緒にDartの奥深い世界を覗いてみましょう!
—
1. 他の言語とは違う? Dartの「何も返さない」の世界
プログラムを書いていると、「この処理に入ったら、もう正常には戻ってこない(=絶対に例外を投げる、またはプログラムを終了する)」という状況に出くわしますよね。
例えば、バリデーションエラーを処理する関数や、致命的なシステムエラーを知らせる関数などです。
// ありがちなエラー処理関数
void throwError(String message) {
throw Exception(message);
// ここで処理は終わり、呼び出し元には何も「値」は返らない
}
このコード、一見すると問題なさそうに見えますよね。戻り値がないので `void` を指定しています。
しかし、Dartの厳格な型システムとコンパイラの視点に立つと、少し物足りない部分があるのです。それを次の章で見ていきましょう。
—
2. `void` と `Never` の決定的な違い
初心者の方がつまづきやすいのが `void` と `Never` の違い です。ここが今回の記事の最重要ポイントですよ!
- `void` 型: 「この関数は、意味のあるデータを返さない」という意味です。関数自体は正常に終了し、呼び出し元に制御が「戻ってきます」。
- `Never` 型: 「この関数は、絶対に正常終了しない(=呼び出し元に制御が戻らない)」という意味です。
コンパイラはどう見ているか?
Dartのコンパイラは非常に優秀です。もし関数が `void` を返すなら、「この関数の実行が終わった後は、次の行のコードを実行するんだな」と解釈します。
一方で、関数が `Never` 型 を返すと知った瞬間、コンパイラはこう考えます。
> 「おっ、この関数が呼ばれたら最後、二度とここには戻ってこないんだな。じゃあ、この関数より後ろにあるコードは『絶対に到達不可能なコード(Dead Code)』だな!」
この「到達不可能性(Unreachability)」をコンパイラに正確に伝えることで、Dartの型安全性は極限まで高まるのです。
—
3. 実践! `Never` 型を使った堅牢な例外処理設計
それでは、実際に `Never` 型をどうコードに組み込むのか、具体的な書き方を見てみましょう。
// 戻り値に `Never` を指定する
Never fatalError(String description) {
// ログ出力などの処理
print(‘[CRITICAL]: $description’);
// 例外をスローして強制終了
throw StateError(description);
// ここに到達することは絶対にないため、returnを書く必要もないし、書けない
}
void processUserAge(int? age) {
// 年齢がnullなら、fatalErrorを呼んで処理を中断する
// 戻り値が Never なので、コンパイラは「この下の行では age は絶対的に int である」と確定できる!
int validAge = age ?? fatalError(‘年齢が未設定です!’);
// ここに到達した時点で、age は null ではないことが保証されている
print(‘ユーザーの年齢は ${validAge} 歳です。’);
}
void main() {
// 正常系
processUserAge(25); // 出力: ユーザーの年齢は 25 歳です。
// 異常系(例外がスローされ、プログラムが止まる)
processUserAge(null); // 出力: [CRITICAL]: 年齢が未設定です! からの StateError
}
このコードが美しい理由
`processUserAge` 関数の中を見てください。`age ?? fatalError(…)` と書かれています。
もし `fatalError` の戻り値が `void` だと、コンパイラは「もしかしたらこの後も処理が続くかもしれない」と身構えてしまい、`validAge` が本当に非nullな `int` なのか確信を持てず、型チェックでエラーになることがあります(あるいは追加の安全確認が必要になります)。
しかし、`Never` を使うことで、「右側の処理が走ったら、この関数はここで即座に爆発(終了)するから、左側の変数には確実に値が入っている時しか進まない」という事実を、コンパイラと完全に共有できるのです。
—
4. 陥りがちな文法エラーと注意点
`Never` 型を使い始めの頃に、誰もが一度はやってしまうミスがあります。
やってしまいがちなミス:戻り値に何も書かない、あるいは値を返そうとする
// 【誤り】Never なのに普通に処理が終わろうとしている
Never doSomething() {
print(‘こんにちは’);
// エラー! コンパイラは「Neverなのに正常終了しようとした!」と検知します
}
対策:
`Never` 型を宣言した以上、その中身は必ず「例外を投げる (`throw`)」「無限ループに入る」「プログラムを終了する」のいずれかで、物理的に関数の外へ脱出しなければなりません。
—
まとめ:`Never` を使いこなしてDartを掌中に収めよう
今回は、Dartの `Never` 型を活用した例外処理設計について解説しました。
- `void` は「何も返さない(でも戻ってくる)」
- `Never` は「二度と戻ってこない(到達不能を伝える)」
- `Never` を使うことで、コンパイラがコードの安全性をより深く理解し、型安全なロジックを組むことができる。
一見するとマニアックな機能に見える `Never` 型ですが、大規模なFlutterアプリや堅牢なバックエンドを構築する際には、バグの芽をコンパイル段階で摘み取ってくれる強力な味方になります。
ここをクリアできれば、あなたのDartの基本はもうバッチリマスターできていますよ!自信を持って、次のコードも書いていきましょう。それでは、また次回の記事でお会いしましょう!