最近 A Tour of Go を進めていて,
defer
というステートメントを知った.The Go Blog に詳細な解説記事が載ってたので,読みながら動かしてみた結果を簡単にまとめておく.
defer とは
関数の終了時に遅延実行する機能のこと.直感的に finally
的なアレかと思ったけど,実際に使ってみると全然違った.遅延実行されるタイミングは return
だけじゃなくて panic
もそう.さらに複数の処理をスタックできて LIFO で返ってくる点も興味深かった.
A defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.
defer の特徴
記事にあるサンプルコードを少し修正して試してみた.
- A deferred function's arguments are evaluated when the defer statement is evaluated.
- Deferred function calls are executed in Last In First Out order after the surrounding function returns.
- Deferred functions may read and assign to the returning function's named return values.
上記の通りだけど,
- defer を評価したタイミングの値が保持される
- スタックすることができて LIFO で返ってくる
- 関数の戻り値(変数)にも適用できる
という感じ.
package main import ( "fmt" ) func main() { // === Start sample1() === // 3 // 0 // === Start sample2() === // 0 // 1 // 2 // 3 // 4 // Defer : 4 // Defer : 3 // Defer : 2 // Defer : 1 // Defer : 0 // === Start sample3() === // 4 sample1() sample2() i := sample3() fmt.Println(i) } func sample1() { // `defer` は評価時の値を保持するため `0` のまま出力される fmt.Println("=== Start sample1() ===") i := 0 defer fmt.Println(i) i++ i++ i++ fmt.Println(i) return } func sample2() { // `defer` は複数をスタックすることができ、LIFO で返ってくる fmt.Println("=== Start sample2() ===") for i := 0; i < 5; i++ { fmt.Println(i) defer fmt.Println("Defer : ", i) } } func sample3() (i int) { // `defer` で返り値 i に対してインクリメントが行われるため、`4` が返る fmt.Println("=== Start sample3() ===") defer func() { i++ i++ i++ }() return 1 }
defer / panic / recover
記事にあるサンプルコードを写経した.defer
をエラーハンドリングの panic
と recover
と組み合わせた例で参考になった.
重要なのは panic
時にも defer
が呼び出されるところで,g()
で panic
が呼ばれると g()
のスコープ内で定義された defer
が呼び出される.そして f()
の中で defer
に recover
を定義してるので,エラーハンドリングできている.記事にある通り f()
の defer
を消すと,その時点で panic
となり main()
には戻れなくなる.
package main import ( "fmt" ) func main() { // Calling g. // Printing in g 0 // Printing in g 1 // Printing in g 2 // Printing in g 3 // Panicking! // Defer in g 3 // Defer in g 2 // Defer in g 1 // Defer in g 0 // Recovered in f 4 // Returned normally form f. f() fmt.Println("Returned normally form f.") } func f() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() fmt.Println("Calling g.") g(0) fmt.Println("Returned normally from g.") } func g(i int) { if i > 3 { fmt.Println("Panicking!") panic(fmt.Sprintf("%v", i)) } defer fmt.Println("Defer in g", i) fmt.Println("Printing in g", i) g(i + 1) }
まとめ
記事に書かれてる以下の表現が凄く良いなと思った.
パワフルな魔法を使うのではなく,直感的で予測可能な記述を追求するのが Go のスタイルなんだなーと!
The behavior of defer statements is straightforward and predictable.