Goにはいくつかプログラムを終了させる手段が存在する。
プログラムを終了させるときにどれを選べばいいか調べてみた。結論から言うとmain
関数内でdefer
を使わずos.Exit
関数を呼ぶ。
TL;DR
- Goで意図的にプログラムを終了させることができる処理は次のとおり
main
関数内でdefer
を使わずos.Exit
関数を呼ぶのが一番良いと思うpanic
関数は終了コードを明示的に決めることができない- 一方ですべてのゴルーチンを停止してくれるらしい
runtime#Goexit
関数をmain
関数で呼ぶのは行儀が悪そう-
Calling Goexit from the main goroutine terminates that goroutine without func main returning.
defer
宣言済みの関数に関してはすべて実行してから終了してくれるらしい
-
os#Exit
関数は任意の終了コードでプログラムをただちに終了することができるdefer
宣言済みの関数は実行されない
なお、すでにまとめている方のブログ記事も存在する。
また、この記事の調査内容は2021年6月時点で最新のGo1.16時点の各仕様である。
Goのプログラムを終了させる
私はGoのプログラムを終了させるときはいつもos#Exit
関数を使っていた。
「defer
ちゃんと動くしpanic
じゃだめなんですか?」という質問を受けたときちゃんと答えられなかったので仕様を調査してみた。
Goでプログラムを終了させる方法
main
関数をそのまま終了する、セグフォなどで異常終了する、などを除いて、コードの表現としてプログラムを終了させる方法は主に3つある。
os#Exit
関数で終了するpanic
関数で終了するruntime#GoExit
関数で終了する
それぞれの仕様を確認してみた。
os#Exit
関数で終了する
一番オーソドックスな方法だと思っているが、仕様をみると、defer
済みの関数については実行されないと記載があった。
The program terminates immediately; deferred functions are not run.
なので、main
関数でdefer
文と併用してos#Exit
関数を実行すると意図しない挙動をする可能性がある。また、「immediately」なので他のゴルーチンの終了などは待機してくれない。
panic
関数で終了する
調査前はpanic
関数で終了するほうがめちゃくちゃな終了をすると思っていた。
しかし仕様を呼んでみると意外とdefer
済みの関数などはちゃんと呼ばれるようだ。
When a function F calls panic, normal execution of F stops immediately. Any functions whose execution was deferred by F are run in the usual way, and then F returns to its caller.
しかし、終了コードをプログラマが指定することはできない。
runtime#GoExit
関数で終了する
直接使ったことはないが、runtime#GoExit
関数でもプログラムを終了することができる。testing
パッケージのFatal
関数を呼ぶと内部で実行されるようだ。
https://github.com/golang/go/blob/go1.16.5/src/testing/testing.go#L710-L742
仕様を読むと、main
関数で呼ぶ場合、怪しい挙動をするようなのでこれはプログラムを終了させるときに使わないほうがよさそうだ。
Calling Goexit from the main goroutine terminates that goroutine without func main returning. Since func main has not returned, the program continues execution of other goroutines. If all other goroutines exit, the program crashes.
結論
defer
の利用に注意しながらmain
関数でos#Exit
関数を実行するのがよさそうだった。
コマンドラインならばたとえばこんな感じでよいのではないだろうか。
func main() {
os.Exit(run(os.Args, os.Stdin, os.Stdout, os.Stderr))
}
func run(args []string, inStream io.Reader, outStream, errStream io.Writer) int {
// do anything...
}
終わりに(余談)
panic
のような予約語の機能の説明はどこを見ればよいのかわかっていなかった。
builtin
パッケージを見るとpanic
を始め、cap
やappend
と言ったビルドイン関数の明文化された仕様を読むことができるのを知れてよかった。