Go の panic / defer / recover の動作を理解する
3つの仕組みの関係¶
panic → goroutineのスタックを巻き戻しながら各フレームのdeferを実行
defer → 関数が終了する直前に必ず実行される(panicでも)
recover → deferの中でpanicを捕捉して通常処理に戻す
panic¶
実行を即座に中断し、goroutineのスタックを巻き戻す。
func main() {
fmt.Println("start")
panic("something went wrong") // ここで停止
fmt.Println("never reached") // 実行されない
}
nilポインタ参照、範囲外アクセスなどもruntime panicとして発生する。
defer¶
関数が終了するタイミング(正常終了・panic・return)で実行されることが保証される。
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // どのreturnパスでも必ずClose
// ... 処理
return nil
}
deferはLIFO(後入れ先出し) で実行される。
よくある罠: ループ内のdefer¶
// NG: ループが終わるまでCloseされない
for _, path := range paths {
f, _ := os.Open(path)
defer f.Close() // ← 関数が終わるまで溜まり続ける
}
// OK: クロージャに切り出す
for _, path := range paths {
func() {
f, _ := os.Open(path)
defer f.Close() // ← この無名関数が終わるとすぐClose
}()
}
recover¶
defer の中で呼ぶと、panicを捕捉してpanicの値を返す。
recoverが呼ばれるとスタックの巻き戻しが停止し、通常実行に戻る。
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
return a / b, nil // b=0ならpanic
}
func main() {
result, err := safeDiv(10, 0)
fmt.Println(result, err) // 0, recovered from panic: runtime error: integer divide by zero
}
recover が効かないケース¶
deferの外で呼んでも nil を返すだけ(panicは止まらない)- 別goroutineのpanicは捕捉できない
実行順序の完全な例¶
func example() {
defer fmt.Println("defer 1")
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()
defer fmt.Println("defer 3")
panic("boom")
fmt.Println("after panic") // 実行されない
}
// 出力:
// defer 3
// recovered: boom
// defer 1
panicが発生するとdeferがLIFOで実行される。recoverを含むdeferがpanicを捕捉すると、残りのdeferも続けて実行される。
いつ使うか¶
- panic: プログラムの不変条件が壊れた場合(バグ)。通常のエラー処理にはerrorを返す
- defer: リソース解放(Close、Unlock)、ログ、タイミング計測
- recover: ライブラリの境界でpanicを封じ込める(HTTPハンドラのミドルウェアなど)
// HTTPサーバーでpanicを500エラーに変換するミドルウェア
func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
http.Error(w, "Internal Server Error", 500)
log.Printf("panic recovered: %v", r)
}
}()
next.ServeHTTP(w, r)
})
}