never 型が必要になる場面

never は「絶対に起こらない」ことを表す型だ。一見すると使い道がなさそうだが、実際には型の網羅性チェックやエラー処理で重要な役割を果たす。

never 型とは何か

never は「値が存在しない」ことを示す型である。関数が正常に終了しない場合や、論理的にあり得ない状態を表現するために使う。

// 例外を投げる関数は never を返す
function throwError(message: string): never {
  throw new Error(message);
}

// 無限ループも never を返す
function infiniteLoop(): never {
  while (true) {}
}

void と混同しやすいが、意味がまったく違う。void は「戻り値がない」、never は「そもそも戻ってこない」である。

void

関数が正常に終了するが、戻り値がない。return;return undefined; で終わる関数。

never

関数が正常に終了しない。例外を投げるか、無限ループで終わらない関数。

網羅性チェックでの活用

never の最も実用的な使い方は、switch 文の網羅性チェックだ。

type Status = "pending" | "approved" | "rejected";

function handleStatus(status: Status): string {
  switch (status) {
    case "pending":
      return "処理中";
    case "approved":
      return "承認済み";
    case "rejected":
      return "却下";
    default:
      // ここに到達するはずがない
      const _exhaustive: never = status;
      return _exhaustive;
  }
}

すべてのケースを処理していれば、default に到達することはない。つまり statusnever 型になる。

ここで威力を発揮するのは、Status に新しい値が追加されたときだ。

type Status = "pending" | "approved" | "rejected" | "cancelled";  // 追加

function handleStatus(status: Status): string {
  switch (status) {
    case "pending":
      return "処理中";
    case "approved":
      return "承認済み";
    case "rejected":
      return "却下";
    default:
      // エラー: Type 'string' is not assignable to type 'never'
      const _exhaustive: never = status;
      return _exhaustive;
  }
}

"cancelled" を処理していないため、default 節で status"cancelled" 型になる。これを never に代入しようとするとコンパイルエラーになり、処理漏れを検出できる。

条件型での never

条件型でフィルタリングを行うとき、never が活躍する。

type NonNullable<T> = T extends null | undefined ? never : T;

type A = NonNullable<string | null>;  // string
type B = NonNullable<number | undefined>;  // number

ユニオン型から特定の型を除外したいとき、該当する型を never に変換する。never はユニオン型に含まれていても無視されるため、結果的にフィルタリングが実現する。

// string | never は string と同じ
type Test = string | never;  // string

関数オーバーロードでの never

あり得ない引数の組み合わせを禁止するためにも使える。

function process(value: string): string;
function process(value: number): number;
function process(value: string | number): string | number {
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  return value * 2;
}

// boolean を渡すとエラー
// process(true);

型システムが「この組み合わせは存在しない」と判断した結果が never として現れる。

実践的なパターン

アサーション関数

asserts キーワードと組み合わせて、値の検証を行う関数を書ける。検証に失敗したら例外を投げ、never を返す。

エラーハンドリング

回復不能なエラーを投げる関数の戻り値として使う。呼び出し元に「この関数は戻ってこない」と伝えられる。

never は TypeScript の型システムにおける「底」の型だ。すべての型のサブタイプであり、どんな型にも代入できるが、逆に never には何も代入できない(never 自身を除く)。この特性を理解しておくと、高度な型パズルも読み解けるようになる。