void
型とはほとんどの場合において「値がないこと」を示す特別な型である。このとき、関数の戻り値の型としてvoid
型を指定したときに「値を返さない」と説明する人をしばしば見かける。
これは解釈次第では間違いではないのだが、「誤解が生じやすい説明」であることを今回は書いていこうと思う。
- 前置き
- void型関数の定義
- void型関数の結果を返すことも可能
- そもそも「値を返さない関数」はvoid型でなくても作れる
- 「値を返さない関数」には正体がある
- おまけ:「void」と名乗りながら実はvoid型でない型
- 参考
void
型関数の定義
void
型関数は以下のように定義する。
void f(void) { /* 処理内容 */ }
ほかの関数と違うのは、ほとんどの場合において「void
型の変数」というものは存在しないので変数に結果を代入できないことである*2。
しかし値として返すことができないというわけではない。早期returnなどをしたい場合にreturn;
を記述することでvoid
型の値を返すことができる。
void f(void) { if (/* 条件 */) { /* 処理内容 */ return; // 早期returnしたい場合に左のように書く } else { /* 処理内容 */ return; } }
ただ、void
型は先ほども書いた通り「値がないことを示す型」なので、戻り値として返すことはできるものの、値としては存在しない。そのため、()
という値を返すユニット型*3の関数とは異なる。
void
型関数の結果を返すことも可能
JavaやPHP、C#などではできないが、void
型関数の結果を戻り値として返すことも可能である。
/** * 結果を戻り値として返される関数 */ void f(void) { /* 処理内容 */ } /** * void型関数の結果を戻り値として返す関数 */ void g(void) { /* 処理内容 */ return f(); }
これ自体にはあまりメリットがなさそうなので、JavaやPHP、C#で関数の結果を戻り値として返すことを禁止するのもわからなくはないが、たとえば以下のようなコードを書くと場合によっては多条件分岐の見栄えが良くなりそうではある(・・・はプログラムの省略を示す)。
// 処理番号 #define PROCESS1 0 #define PROCESS2 1 #define PROCESS3 2 ・ ・ ・ // 処理 void process1(/* 引数 */) { /* 処理内容 */ } void process2(/* 引数 */) { /* 処理内容 */ } void process3(/* 引数 */) { /* 処理内容 */ } ・ ・ ・ /** * 処理を実行する関数 */ void run_process(int proc_num) { switch (proc_num) { case PROCESS1: return process1(/* パラメータ */); case PROCESS2: return process2(/* パラメータ */); case PROCESS3: return process3(/* パラメータ */); ・ ・ ・ default: return; // 何もしない } }
そもそも「値を返さない関数」はvoid
型でなくても作れる
そもそも論だが、「値を返さない関数」はvoid
型じゃなくても作れる。無理だろうと思われるかもしれないが、以下のコードを見てほしい。
int f(void) { // ここで無限ループする(つまり、"値を返さない") while (1) { /* 処理内容(break/returnなし) */ } return 0; }
この関数はint
型の関数でこそあるものの、無限ループのために値を返せない、つまり、「値を返さない関数」と説明できる関数になっているのである。
ここから何が言いたいのかと言うと、void
型の関数を「値を返さない関数」と説明すると、int
型の関数も場合によってはそうだと説明できるため、矛盾するし、そういう意味じゃないにしても誤解が生じやすいことは確かだ。
「値を返さない関数」には正体がある
実は「値を返さない関数」は型理論的には、⊥型*4の関数の特徴である。この⊥型の関数こそが「値を返さない関数」としての役割を果たしており、これによってプログラムは無限ループや例外処理などを実現することが可能となるのである。
しかしちょっと待ってほしい。⊥型がそうなのであればint
型やvoid
型などの関数はなぜ「値を返さない関数」として定義できるのか?と多くの方が疑問を持ったことだろう。
実は型理論的には、⊥型はint
型やvoid
型などすべての型の部分型である。つまり、ほとんどのプログラミング言語では⊥型を含むすべての型が⊥型の特徴を持っているのである。
したがって、「値を返さない関数」はvoid
型の関数としての特徴ではなく⊥型の関数としての特徴であり、「void
型の関数は『値を返さない関数』である」という説明はあまり適切でないことがわかる。
おまけ:「void」と名乗りながら実はvoid
型でない型
ここからは蛇足であるが、一つ注意点がある。
SwiftのVoid
型は()
(空のタプル)型の型エイリアス、つまり、ユニット型である。そのため、SwiftのVoid
型は実はvoid
型ではないため、Swiftプログラマは注意する必要がある*5。
参考
- 型システム入門 −プログラミング言語と型の理論−
- ボトム型 - Wikipedia
- 記号のみ参照。それ以外の記述はところどころ誤りがあるため注意が必要。
- Void型 | Swiftの始め方