never型は「絶対に起こらない」を表す型
まず一番大事なイメージからいきます。never 型は、「ここには“値が存在しないはずだ”」「ここには“絶対に到達しないはずだ”」という場所を型で表すための特別な型です。
string や number は「こういう値が入るよ」という型ですが、never は逆に「ここに値が入ることはありえない」という“ゼロ”の型です。
だからこそ、普通にコードを書いているときにはあまり出てきません。
でも、「ありえないはずのパターンを検出する」「絶対に返ってこない関数を表す」ときに、めちゃくちゃ役に立ちます。
基本1:絶対に戻ってこない関数の戻り値型としての never
例:必ず例外を投げる関数
function fail(message: string): never {
throw new Error(message);
}
TypeScriptこの関数は、throw した時点で処理が止まり、呼び出し元には「戻ってこない」関数です。
だから戻り値型は never になります。
ポイントは、void との違いです。
void は「戻り値を使わないけど、関数自体はちゃんと終わる」。never は「関数が“正常には終わらない”ので、戻り値という概念自体がない」。
もう一つ例を出します。
function infiniteLoop(): never {
while (true) {
// ずっと回り続ける
}
}
TypeScriptこの関数も、呼び出したら最後、処理が戻ってきません。
こういう「絶対に終わらない」「必ず例外で止まる」関数の戻り値型が never です。
基本2:条件分岐の「漏れ」を検出するための never
ユニオン型と switch の組み合わせ
never が本領発揮するのは、「本当はありえないはずのケースが、実は漏れている」ことを教えてくれるときです。
例えば、こんなユニオン型があるとします。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number };
TypeScriptこれを kind で分岐して処理したいとき、こう書きます。
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
}
}
TypeScript今はこれでいいのですが、あとから型をこう変えたとします。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number }
| { kind: "triangle"; base: number; height: number };
TypeScriptでも、area 関数の中身を直し忘れたらどうなるでしょう?triangle の処理が抜けたままになります。
ここで never を使った「抜け漏れ検出テクニック」が効いてきます。
応用:neverを使った「網羅性チェック」
最後に「ありえないはず」の場所を作る
さっきの area 関数を、こう書き換えます。
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
default:
return assertNever(shape);
}
}
function assertNever(x: never): never {
throw new Error(`Unexpected object: ${JSON.stringify(x)}`);
}
TypeScriptここでのポイントは assertNever の引数の型です。
function assertNever(x: never): never
TypeScript「x は never 型でなければならない」と宣言しています。
つまり、「ここに渡される値は“ありえない”はずだ」という意味です。
ところが、Shape に triangle を追加したのに switch に case "triangle" を書き忘れると、default に渡ってくる shape の型は never ではなく { kind: "triangle"; ... } になります。
すると、ここでコンパイルエラーになります。
Type ‘{ kind: “triangle”; base: number; height: number; }’ is not assignable to type ‘never’.
これがめちゃくちゃ重要で、
「本当は“ありえないはず”の場所に、実は新しいパターンが流れ込んでいる」ことを、TypeScript が教えてくれるわけです。
このテクニックは「網羅性チェック(exhaustive check)」と呼ばれていて、
ユニオン型を使うときの定番パターンです。
neverは「何にでも代入できる」が「何からも代入されない」
ここは少しだけ理論寄りですが、感覚として知っておくと便利です。
never は「値が一つも存在しない型」なので、
「どんな型にも代入できる」が、「どんな型からも代入できない」という性質を持っています。
const n: never = 1; // エラー: number は never に代入できない
const s: string = n; // OK: never は string に代入できる
const u: { a: number } = n; // OK: never はどんな型にも代入できる
TypeScript「どんな型にも代入できる」というのは、
「ここには絶対に値が来ない前提だから、何にでも“なれる”」というイメージです。
この性質があるからこそ、さっきの assertNever のような関数が書けます。
function assertNever(x: never): never {
throw new Error("Unexpected value");
}
TypeScriptx に何かが渡ってきた時点で、「それはおかしい」と分かる。
「ここに値が来るのはバグだ」という場所を、型で表現できるのが never の強さです。
初心者がまず掴んでおきたい never の感覚
いきなり全部を使いこなそうとしなくて大丈夫です。
まずは、こんなふうに捉えておくといいです。
never は、「ここには値が来ないはず」「ここには到達しないはず」という“ありえない場所”を表す型。
必ず例外を投げる関数や、無限ループする関数の戻り値型として現れる。
ユニオン型と switch を組み合わせたときに、「分岐漏れ」を検出するためのトリガーとして使える。
そして一番大事なのは、
「never が出てきたら、“ここは本当にありえない場所のはずなんだけどな?”と立ち止まる」という姿勢です。
TypeScript が never を見せてくるとき、
それはたいてい「設計上の“ありえないはず”が、本当に守られているか?」を問いかけてきている瞬間です。
そこに気づけるようになると、never はただの難しそうな型ではなく、
「バグの芽を教えてくれるセンサー」に見えてきます。
