deferで後始末
後で実行する予約
Go
func process() {
fmt.Println("開始")
defer fmt.Println("終了")
fmt.Println("処理中")
}プレーンテキスト
開始
処理中
終了defer を付けた行は、その場では実行されません。関数を抜けるときに実行されます。書いた場所は2行目なのに、出力は最後に来ます。
何のためにあるのか
開いたものを閉じる、確保したものを返す、という後始末のためです。
Go
f := openFile("items.csv")
defer f.Close()
// この先どこで return しても、Close は必ず呼ばれる後始末を開く処理のすぐ下に書けるのが利点です。関数の最後に書くと、途中に return を足したときに素通りされます。defer ならその心配がありません。
第8章でファイルやリクエストの本文を扱うとき、この形が何度も出てきます。
複数書くと逆順になる
Go
defer fmt.Println("1番目")
defer fmt.Println("2番目")
defer fmt.Println("3番目")プレーンテキスト
3番目
2番目
1番目後に書いたものから実行されます。積み上げたものを上から降ろす順番です。
開いた順に閉じると困ることがあるので、この順序になっています。A を開いて B を開いたなら、閉じるのは B が先であるべき、という考え方です。
引数はその場で確定する
ここが最も間違えやすい点です。
Go
count := 1
defer fmt.Println(count) // 1 が出る
count = 2実行されるのは最後ですが、引数の値は defer を書いた時点で決まります。後で count を変えても、出力は 1 のままです。
変わってほしいなら、関数ごと囲みます。
Go
count := 1
defer func() { fmt.Println(count) }() // 2 が出る
count = 2中の count は実行時に読まれるので、変更が反映されます。末尾の () は「この場で呼び出す形にしておく」という意味で、これを書き忘れると何も起きません。
第7章のゴルーチンで、この違いがそのまま出てきます。
要件
deferを使って終了の行を予約する[開始] 名前処理中[終了] 名前の3行を\nでつないで返す- 最後に余分な改行を付けない
入出力例
processLog("集計") → "[開始] 集計
処理中
[終了] 集計"
processLog("検索") → "[開始] 検索
処理中
[終了] 検索"ヒント
編集 ゆめさく編集部