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と言ったビルドイン関数の明文化された仕様を読むことができるのを知れてよかった。