TypeScriptの「Never」型を掌握する:例外スロー専用関数でコードの「死角」を消し去る
こんにちは。TypeScriptの世界へようこそ。
コードを書いていると、「ここは本来到達しないはずの場所だ」「この関数が値を返すことはありえない」という場面に遭遇しますよね。
そんな時、なんとなく `any` を使ったり、適当に `void` を返したりしていませんか?
実は、TypeScriptには「この処理の先は、プログラムの終わり(あるいは中断)である」とコンパイラに明示的に伝えるための最強の武器があります。それが `never` 型です。
今日は、例外を投げる関数を例に、なぜ `never` があなたのコードを「型安全な要塞」に変えるのか、その本質を解き明かしていきましょう。
—
1. なぜ `void` ではダメなのか?
まず、よくある間違いから見てみましょう。必ずエラーを投げる関数を作ったとき、戻り値を `void` にしてしまうケースです。
function raiseError(message: string): void {
throw new Error(message);
}
function processValue(value: string | null) {
if (value === null) {
raiseError(“値がありません”);
}
// ここで TypeScript は「value はまだ null かもしれない」と疑う
console.log(value.length); // エラー! ‘value’ is possibly ‘null’.
}
お気づきでしょうか。`raiseError` を呼んだ後、TypeScriptは「この関数は処理を完了して戻ってくるかもしれない」と予想しています。そのため、`null` のチェックが漏れていると判断し、型エラーを吐くのです。
ここでのポイント: `void` は「戻り値がない(=処理が終わって戻ってくる)」ことを意味しますが、`never` は「処理が終わって戻ってくること自体がありえない」ことを意味します。
—
2. 「never」型でコンパイラに事実を伝える
`never` を使うと、TypeScriptの挙動は劇的に変わります。
// 戻り値を never と定義する
function unreachable(message: string): never {
throw new Error(message);
}
function processValue(value: string | null) {
if (value === null) {
unreachable(“値がありません”);
}
// TypeScript は「unreachable は絶対に呼び出し元に戻らない」と理解している
// そのため、ここでは value が null である可能性は排除されている
console.log(value.length); // OK!型安全にアクセス可能
}
コンパイラは `unreachable` を呼び出した時点で「あ、ここから先は実行されないんだな」と理解します。そのため、その後のコードでは `value` が `string` であることが確定するのです。これが `never` の持つ「型推論のフローを制御する力」です。
—
3. 「網羅性チェック(Exhaustiveness Check)」という真の威力
`never` の真価は、`switch` 文や `if-else` 分岐の「漏れ」を見つけるときに発揮されます。
type Shape = “circle” | “square”;
function getArea(shape: Shape) {
switch (shape) {
case “circle”: return Math.PI;
case “square”: return 1;
default:
// ここで shape は never 型になっているはず
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
もし将来、`Shape` 型に `”triangle”` が追加されたらどうなるでしょうか?
`switch` 文で処理し忘れると、`default` 節に `”triangle”` が流れ込みます。しかし、`_exhaustiveCheck` は `never` 型なので、「`string` 型を `never` 型に代入しようとしています」というコンパイルエラーが発生します。
「未来の開発者がコードを追加したとき、漏れがあればコンパイル時に教えてくれる」。これこそが、堅牢なシステムを支える TypeScript の極意です。
—
4. 陥りやすい罠:戻り値がないのに「never」は使えない
初心者の方がよくやってしまうのが、「処理が終わらない関数」ではなく「処理が終了する関数」に `never` をつけてしまうことです。
// NG例:戻り値があるのに never と書くと死ぬ
function sayHello(): never {
console.log(“Hello!”);
// 本当に戻り値がないなら void が正解
}
- void: 「何も返さない」が、関数は正常に終了する。
- never: 「そもそも関数が終了地点に到達しない(例外スローや無限ループ)」。
この違いを意識するだけで、あなたのコードの解像度は一段階上がります。
—
まとめ:あなたのコードに「到達不能」という明快な意思を
`never` 型を使いこなすということは、コンパイラに対して「私のロジックにおいて、ここはどういう状態になるか」を正確に翻訳して伝えることに他なりません。
1. 例外を投げる関数には必ず `: never` をつける。
2. `default` 節で `never` を活用して、網羅性を保証する。
この2つを意識するだけで、実行時に「想定外の null が入り込んだ!」と慌てることは激減します。ここをクリアすれば、あなたはもう TypeScript の型システムの深淵に一歩足を踏み入れたと言っても過言ではありません。
さあ、自信を持って、型安全なコードを書いていきましょう!何か不明点があれば、いつでも聞いてくださいね。応援しています。