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章のゴルーチンで、この違いがそのまま出てきます。
演習では表示の順番に集中する
fmt.Println に複数の値を渡すと、間に空白を入れて1行で表示します。
Go
name := "集計"
fmt.Println("[開始]", name) // [開始] 集計演習では printProcessLog に終了表示を予約する1行を足します。processLog は表示を文字列にして採点へ渡す補助関数です。入出力の切り替えや文字列の組み立ては補助関数に任せ、変更しなくて構いません。
要件
printProcessLogの開始表示のすぐ後ろで、defer fmt.Println(...)を使って終了表示を予約する[開始] 名前、処理中、[終了] 名前の順で3行を表示する- 採点用の補助関数
processLogはそのまま使う
ヒント
編集 ゆめさく編集部