Goのdeferを図解で理解する
はじめに
Go言語を学び始めると、必ず出会う便利な機能 defer
。ファイルのクローズ処理やDBコネクションの切断など、後片付け処理を忘れずに実行できるので非常に強力です。
ですが、複数の defer
が出てきたり、関数をまたいだりすると、「あれ、どの処理がどの順番で実行されるんだっけ…?🤔」と混乱した経験はありませんか?
この記事では、そんな defer
の実行順序のルールを、シンプルなコード例と図解でスッキリと解説します。この記事を読み終わる頃には、defer
の動きに迷いがなくなっているはずです!
defer
の絶対ルールは「後入れ先出し(LIFO)」
defer
の実行順序を理解するための最も重要なルールは、**「後入れ先出し(LIFO: Last-In, First-Out)」**です。
これは、机の上に本を積み上げていく様子をイメージすると分かりやすいです。
- 本を積む(
defer
で処理を登録する) - 本を取る時は、一番**後から積んだ本(一番上にある本)**からしか取れません。
defer
もこれと全く同じで、後から defer
で指定した処理ほど、先に実行されます。
LIFOについてアルゴリズムの領域でよく出てくるので、詳細は以下で確認してください。
コードベースで確認してみる
百聞は一見にしかず、ということで実際のコードを見てみましょう。
package main
import "fmt"
func main() {
fmt.Println("main関数の処理を開始します。")
defer fmt.Println("1番目のdefer: 最初に登録されました。") // 最後に実行される
defer fmt.Println("2番目のdefer: 2番目に登録されました。")
defer fmt.Println("3番目のdefer: 最後に登録されました。") // 最初に実行される
fmt.Println("main関数の処理がもうすぐ終わります。")
}
実行結果は以下です。
main関数の処理を開始します。
main関数の処理がもうすぐ終わります。
3番目のdefer: 最後に登録されました。
2番目のdefer: 2番目に登録されました。
1番目のdefer: 最初に登録されました。
defer
で登録した処理が、見事に逆の順番で実行されているのがわかります。
Goは defer
文を見つけるたびに、その処理を「遅延実行リスト(スタック)」に積み上げていき、関数の最後にリストの上から順に実行します。
図で理解するLIFOの動き
この「後入れ先出し」の動きを、図で見てみましょう。処理の流れが視覚的にイメージできるはずです。
defer
が有効なのは「その関数の中だけ」
LIFOと並んで重要なのが、defer
はそれが書かれている関数が終了する時に実行されるというルールです。これを「関数スコープ」と呼びます。
別の関数の中で書かれた defer
は、呼び出し元の関数の終了タイミングとは無関係です。
package main
import "fmt"
func subFunction() {
fmt.Println(" [subFunction] 開始")
defer fmt.Println(" [subFunction] のdeferが実行されました。") // subFunctionの終了時に実行
fmt.Println(" [subFunction] 終了")
}
func main() {
fmt.Println("[main] 開始")
defer fmt.Println("[main] のdeferが実行されました。") // main関数の終了時に実行
subFunction() // subFunctionを呼び出す
fmt.Println("[main] 終了直前")
}
実行結果は以下です。
[main] 開始
[subFunction] 開始
[subFunction] 終了
[subFunction] のdeferが実行されました。
[main] 終了直前
[main] のdeferが実行されました。
subFunction
の defer
は、subFunction
が終了した直後に実行されていますね。main 関数の最後まで待っているわけではありません。
defer
の動き(図解)
関数をまたぐこの動きも、図で見ると一目瞭然です。ポイントは、deferのスタックが関数ごとに用意されているという点です。
図を見ると、subFunction
が自身の defer
を実行して完全に終了してから、処理が main
関数に戻っていることがよく分かります。
まとめ
defer
の実行順序を理解するためのポイントは、たった2つです。
- 後入れ先出し (LIFO): 同じ関数内では、後から defer したものが最初に実行される。
-
関数スコープ:
defer
は、それが書かれている関数が終了する時に実行される。
この2つのルールを覚えておけば、もうdefer
の動きに迷うことはないと思います。Goでの開発がもっと楽しく、効率的になるはずです。
また、忘れたときはいつでもこの記事に戻ってきてください!
Discussion