TypeScript には「戻り値がない」ことを表す void と「値が存在し得ない」ことを表す never がある。
両者は似ているようでまったく異なる意味を持つ。本記事では違いと使い方を整理する。
1. void 型
- 値を返さない関数の戻り値型を表す
- 実際には JavaScript の仕様で
undefinedが返ることもあるが、基本的に戻り値を利用しない関数に使う
function logMessage(msg: string): void {
console.log(msg);
}
処理は正常に終了するが、返り値はない。
2. never 型
- 値が決して存在しないことを表す型である
- 主に 処理が戻らない関数 に使われる
never 型の使い方
① 例外を投げる関数
function throwError(message: string): never {
throw new Error(message); // ここで処理が終了するので戻らない
}
② 無限ループ
function infiniteLoop(): never {
while (true) {}
}
③ 型の網羅性チェック
- ユニオン型の全ケースを扱っているかチェックする際に使う
- 新しい値が追加された場合、コンパイル時に漏れを検出できる
type Status = 'draft' | 'published';
function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}
function handleStatus(s: Status) {
switch (s) {
case 'draft':
console.log('下書き');
break;
case 'published':
console.log('公開');
break;
default:
assertNever(s); // 新しいケースが追加されるとここで型エラー
}
}
-
TypeScript のコンパイラは
Statusが'draft' | 'published'しかないと知っているので、defaultに本当に到達するかどうかは実行時には関係なく、型チェック上では「ここにはnever型しか来ない」(= 概念的には「ここには何も値が入らない」ということ)と判断する。
→ サンプルコードであれば、defaultまで到達不可能ということを表している。 -
将来
Statusに'deleted'を追加した場合、コンパイル時にassertNever(s)で型エラーになる。
→defaultに到達する可能性があるためコンパイルエラーが発生する。
→ 実際にswitchを実行してdefaultに到達するかどうかは関係ない。
→ コンパイルエラーのため、assertNever関数が実行されるわけではない。
never 型と型チェックの特徴
-
never型には 基本的に値を代入できない - 型が本当に
never(=型上到達不可能な値)の場合のみ代入可能 - 到達可能な値(例えば
enumに新しい値が追加された場合など)を代入するとコンパイルエラーになる - これは 実行時ではなく、コンパイル時の型チェック によるエラーである
3. never と void の違い
-
void: 処理は終了するが値は返さない -
never: 処理が戻らないので値は存在しない
| 型 | 意味 | 例 |
|---|---|---|
void |
戻り値なし(処理は終了する) | ログ出力関数 |
never |
値が存在し得ない(処理が戻らない / 到達不能) |
throw / 無限ループ / 網羅性チェック |
4. まとめ
-
void→ 「値を返さない」関数用 -
never→ 「値が存在し得ない」関数や型安全チェックに使用 -
never型は型チェック上の概念であり、実行時の挙動とは別物 -
switch文の網羅性チェックなどに活用すると、将来的に型の漏れをコンパイル時に検出できる