【テクニカル・上級編】関数型における「void」と「undefined」の戻り値の差異と、呼び出し元への影響 – TypeScript コア・型システムの基礎解析バイブル

戻り値の `void` と `undefined`:コンパイラ型評価とランタイム破綻の境界線

TypeScriptの型システムにおいて、`void` と `undefined` は初学者の多くが混同しがちなプリミティブである。しかし、V8などのJavaScriptエンジン内部挙動、TypeScriptコンパイラ(tsc)の型推論メカニズム、そして高階関数における型安全性の観点から見れば、この二者は完全に異世界の概念である。

本稿では、戻り値が `void` である関数においてなぜ `undefined` を返すことが型システムおよび実行時において危険なのか、その低レイヤの真実を解き明かす。

—

1. コンパイラ視点:`void` は「無関心」、`undefined` は「値の存在」

まず、TypeScriptの型定義におけるセマンティクスを再確認する。

  • `undefined`: プリミティブ値の一つであり、JavaScriptの仕様上、未初期化の変数や値が存在しないことを示す具体的な「値」である。
  • 導出された `void`: 関数が「戻り値を意図的に返さない」ことを示すトップレベルの型アノテーションである。

TypeScriptの型チェッカー(`checker.ts`)において、戻り値の型が `void` の関数は、「呼び出し元が戻り値を利用しない(捨て去る)」という契約を結ぶ。

// コンパイル時の型契約の乖離
function executeLog(message: string): void {
console.log(message);
// 型エラーにならない(しかしアーキテクチャ上の地雷)
return undefined;
}

一見、`undefined` は値を持たないため `void` と互換性があるように見える(実際、`strictNullChecks` が無効な場合や、特定の関数型代入性ルールにおいては許容されることがある)。しかし、これがコールバックや高階関数の世界に入った瞬間、型安全性の防壁が崩壊する。

—

2. 高階関数における型汚染:なぜ `void` に `undefined` を混ぜてはいけないのか

次のコードを見てほしい。Array.prototype.forEach や、カスタムのイベントエミッターを想像してほしい。

type EffectCallback = () => void;

// 呼び出し側(フレームワーク層)
function runEffect(callback: EffectCallback): void {
// 戻り値は「捨てられる」ことが前提
const result = callback();

// もしここで result を何かに使おうとしたら?
// 実際には void なので TypeScript は型エラーにするが…
}

ここで、コールバックを渡す側が「何も返さない」代わりに明示的に `undefined` を返したり、値を持つ式(例えば `Array.prototype.push` の戻り値など)をそのまま流し込んだとする。

const items: number[] = [];

// Array.prototype.push は「追加後の配列の長さ(number)」を返す
// しかし、シグネチャ上 () => void を要求する場所にこれを渡すと何が起きるか?
const badCallback: EffectCallback = () => items.push(42);

// TypeScriptの「戻り値のBivariance(双方向性)」または「割愛ルール」により、
// number を返す関数が void を要求する場所に代入可能になってしまうケースがある。

コールバックの戻り値破綻

`void` を返す関数型に `( ) => number` や `( ) => undefined` が代入可能なとき、呼び出し元は「戻り値など存在しない」と仮定してコードを生成する。しかし、実行時には値が返されてしまっている。

これが大規模な非同期パイプラインや、RxJSのようなリアクティブ・ストリームの内部で起きた場合を想像してほしい。イベントループのマイクロタスク・キューにおいて、意図せぬ戻り値がメモリ上に残り続け、ガベージコレクタの最適化パスを阻害する要因になり得る。

—

3. ランタイムとV8エンジン最適化:隠れたパフォーマンス劣化

JavaScriptエンジン(V8など)は、JITコンパイル(IgnitionとTurboFan)の過程で、関数の形状(Hidden Class / Shape)や戻り値の推論を行っている。

  • `void` を返す関数: エンジンは「この関数は値を返さない(寄存器に値をロードする必要がない)」と判断し、インライン化(Inlining)やDead Code Elimination(DCE)の最適化を aggressively に適用できる。
  • `undefined` を返す関数: 明示的に `return undefined;` が書かれている場合、エンジンはローカル変数やレジスタに `undefined`(またはジャンプ先でのポインタ解決)をロードする命令を生成せざるを得ないケースがある。

極限までパフォーマンスが要求されるエッジコンピューティングやリアルタイム・レンダリング・パイプラインにおいて、この無駄な `undefined` の往復は、ホットパス(Hot Path)におけるCPUキャッシュ効率を静かに悪化させる。

—

4. 正しい設計:防壁を構築するTypeScriptコード

この問題を完全に封じ込めるための、シニアエンジニアリングとしての実践的なアプローチを示す。

① `noImplicitReturns` と `noUncheckedIndexedAccess` の徹底

`tsconfig.json` では、コンパイラの網を極限まで厳しく張る。

{
“compilerOptions”: {
“strict”: true,
“noImplicitReturns”: true,
“noUncheckedIndexedAccess”: true,
“exactOptionalPropertyTypes”: true
}
}

`noImplicitReturns` を有効にすることで、`void` 以外の戻り値を持つ関数でパスの途中でreturnが漏れている場合にコンパイルエラーになるだけでなく、`void` を明示した関数内でうっかり `return;` 以外の値を返すことを防ぐ。

② 戻り値型における `void` と `undefined` の厳密な使い分け

// 【NGな設計】戻り値が曖昧
function processStreamData(data: string): void | undefined {
if (!data) return undefined; // 悪臭を放つアンチパターン
console.log(data);
}

// 【Goodな設計】
// 1. 値が存在しない、または失敗する可能性があるなら、明確に `undefined` を型に含める
function safeFindData(id: string): Data | undefined {
const item = database.get(id);
return item ? item : undefined;
}

// 2. 副作用のみを実行し、呼び出し元に戻り値を一切期待させない場合は、完全な `void`
function logTelemetry(event: TelemetryEvent): void {
analytics.send(event);
// return は一切書かない(あるいは制御フロー的に不要)
}

③ `never` との組み合わせによる完全な網羅性チェック

イベントハンドラーやディスパッチャーの型定義では、戻り値に `void` を強制することで、誤ってデータを伝搬させるバグをコンパイルタイムで根絶する。

type EventHandler = (event: T) => void;

const handleClick: EventHandler = (event) => {
// コンパイラが「この関数は何も返してはならない」と強制する
// 誤って `return event.clientX;` などと書いた瞬間にエラーとなる
trackClick(event.clientX, event.clientY);
};

—

結び:型は「仕様書の防壁」である

TypeScriptにおける型システムは、単なるIDEの補完ツールではない。それは、「ランタイムの混沌に対して人間が築き上げる唯一の防壁」である。

`void` と `undefined` の差異を曖昧にすることは、その防壁に自ら小さな亀裂を入れることに等しい。プロパティの有無、非同期処理のチェイン、そして高階関数の安全性を極限まで高めたいのであれば、「何もしないこと」を表す `void` に、決して「何もないという値」である `undefined` を持ち込んではならない。

コードを書くその瞬間から、コンパイラが生成する機械語の息吹を感じ取れ。それこそが、真のTypeScriptマイスターの境地である。

タイトルとURLをコピーしました