【入門編】関数の戻り値に「void」を指定する真の意味と、呼び出し元での無視の挙動 – TypeScript コア・型システムの基礎解析バイブル

こんにちは。TypeScriptの世界へようこそ。

今日は、多くの開発者が「わかったつもり」で通り過ぎてしまいがちな、しかしTypeScriptの型システムの本質がギュッと詰まった「void」という戻り値の真実についてお話ししましょう。

「`void`は値を返さないという意味ですよね?」
半分正解ですが、TypeScriptの深淵においては、その理解だけではいつか足元をすくわれてしまいます。

この記事を読み終える頃には、あなたは`void`を単なる「空っぽの印」ではなく、「呼び出し側との契約」として正しく使いこなせるようになっているはずです。ここをクリアすれば、TypeScriptの基本はバッチリマスターできますよ。

—

1. そもそも「void」とは何を指しているのか?

C言語やJavaを学んだ方にとって、`void`は「何も返さない」ことを強制する厳格な命令に見えるかもしれません。しかし、TypeScript(およびJavaScript)における`void`の振る舞いはもう少し柔軟で、知的です。

TypeScriptにおける`void`の真の意味は、「その戻り値は利用されない(無視される)ことを期待している」という意思表示にあります。

基本の形

まずは、最も一般的な書き方を見てみましょう。

// 基本的なvoidの使い方
function logMessage(message: string): void {
console.log(`Log: ${message}`);
// return文がない、あるいは単に return; と書く場合
}

const result = logMessage(“Hello”);
// result の型は void。実体は undefined です。

ここまでは簡単ですね。では、ここからが本題です。

—

2. 【衝撃】voidなのに値を返せてしまう不思議

TypeScriptには、初心者の方が驚くような挙動があります。それは「戻り値の型がvoidとして定義されているのに、実際には値を返している関数を代入できてしまう」という仕様です。

以下のコードを見てください。

// 1. voidを返すと定義された関数の型
type VoidCallback = () => void;

// 2. 実際には「number」を返す関数
const sower = (): number => {
return 42;
};

// 3. 【注目】これがエラーにならない!
const execute: VoidCallback = sower;

// 4. 実行すると、値はしっかり返ってきている
const value = execute();
console.log(value); // 42 と表示される。でも、TypeScript上の型は void

「えっ、型エラーにならないの?」と思いましたよね。実は、これこそがTypeScriptの設計思想である「代入可能性(Assignability)」の妙技なのです。

なぜこの挙動が許されるのか?

この仕様の最大の理由は、JavaScriptの既存の書き方を壊さないためです。

一番分かりやすい例が、配列の `forEach` です。

const numbers = [1, 2, 3];
const resultList: number[] = [];

// Array.prototype.forEach のコールバックの期待される型は () => void です。
// しかし、pushメソッドは「新しい配列の長さ(number)」を返します。
numbers.forEach(num => resultList.push(num));

もし、`void`が「絶対に値を返してはいけない」という厳格なルールだったら、上記の `forEach` のコードはエラーになってしまいます(`push`が値を返してしまうため)。

いちいち `numbers.forEach(num => { resultList.push(num); return; });` と書くのは苦痛ですよね。だからTypeScriptは、「呼び出し側が『戻り値を使わない(void)』と言っているなら、関数が何を返そうが勝手だ」と容認してくれるのです。

—

3. 「関数宣言」と「関数型」で振る舞いが変わる罠

ここが非常に重要なポイントです。`void`の挙動は、どこに書くかで厳格さが変わります。

パターンA:関数宣言(厳格)

関数を直接定義するときに`void`を指定すると、値を返すと即座に怒られます。

// これはコンパイルエラー!
function literalVoid(): void {
return “Oops!”; // Error: 型 ‘string’ を型 ‘void’ に割り当てることはできません。
}

パターンB:関数型のリテラル(寛容)

先ほどの例のように、変数に対して「型」として`void`を割り当てた場合は、値を返すことが許容されます。

type Callback = () => void;
const looseVoid: Callback = () => “I’m free!”; // OK

この違いは、「自分で定義した関数の中でうっかり値を返してしまうミス」は防ぎつつ、「他の関数に渡すコールバックとしての互換性」は保つという、プロフェッショナルのための絶妙なバランス調整なのです。

—

4. 安全な設計のための指針

「自由なのはわかったけれど、どう使い分ければいいの?」という疑問に、アーキテクトの視点でお答えします。

① 本当に何も返さないなら `void`

基本はこれです。呼び出し側に「この関数の結果を利用しないでね」と伝える最もポピュラーな方法です。

② 「戻り値がないこと」を厳密に保証したいなら `undefined`

もし、その関数が「絶対に何も返してはいけない(またはundefinedのみを返すべき)」と強く制約したい場合は、`void`ではなく`undefined`を戻り値に指定します。

function strictUndefined(): undefined {
// return “something”; // これはエラーになる
return; // これならOK
}

③ 呼び出し元で値を無視することを徹底させる

`void`を返す関数から得た戻り値を、別の変数に代入して計算に使おうとすると、TypeScriptは警告を出してくれます。

function doSomething(): void { return; }

// const val = doSomething() + 10;
// Error: 演算子 ‘+’ を型 ‘void’ および ‘number’ に適用することはできません。

このように、`void`は「実行時の値を制限する」のではなく「コンパイル時の利用方法を制限する」ための強力なタグとして機能するのです。

—

5. まとめ:TypeScriptを掌握する一歩

今回のポイントを整理しましょう。

1. `void`の本質: 「戻り値がない」ことではなく、「戻り値が利用されない」という契約である。
2. 代入の柔軟性: 関数型として定義された `void` は、他の型を返す関数を受け入れることができる(`forEach`などの利便性のため)。
3. 定義の厳格性: 関数宣言で直接 `void` を指定した場合は、値を返すとエラーになる。
4. 設計の使い分け: ゆるやかな制限なら `void`、厳格に制限するなら `undefined`。

TypeScriptの型システムは、単にバグを見つけるための道具ではありません。「コードを通じて、他の開発者(あるいは未来の自分)とどんな対話をするか」を定義するための言語です。

「この関数は何か作業をするけれど、結果には期待しないでね」というメッセージを込めて `void` を使う。その意図が正しく伝わるコードは、とても美しく、メンテナンスしやすいものです。

ここまでの内容を理解できれば、関数の型定義の基礎はもうあなたのものです。自信を持ってコードを書いていきましょう。次は「never型」や「unknown型」といった、さらに深い型定義の冒険があなたを待っていますよ!

また次のレッスンでお会いしましょう。ハッピーコーディング!

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