[Go Quiz] 解説: Defer quiz
この記事は、Twitterで出題した次のGoクイズの解説です。
Quiz(再掲)
次のプログラムを実行した結果として適切なものを選んでください:
- HELLOと表示されて正常終了する
- 何も表示されずにpanicする
- HELLOと表示されてpanicする
- コンパイルエラーになる
func main() {
var f func()
defer f()
println("HELLO")
f = func() { recover() }
}
このクイズを作ったきっかけ
このクイズを作ったきっかけはyappliさまのTech Blogでした。
とてもわかりやすい記事をありがとうございました。
解説
https://go.dev/ref/spec に基づいて一行ずつ見ていきます。根拠となる箇所は逐一引用していくので興味のある方は検索をかけてみてください。
func main() {
var f func()
defer f()
println("HELLO")
f = func() { recover() }
}
var f func()
変数宣言
まず変数宣言var f func()
を見ます。型func()
を持つ変数f
の宣言ですが、右辺の式が特定されていません。
そのようなときには、f
は型func()
のゼロ値で初期化されます。
If a list of expressions is given, the variables are initialized with the expressions following the rules for assignment statements. Otherwise, each variable is initialized to its zero value.
型func()
のゼロ値はnil
ですから、これはvar f func() = nil
としたのと同じことになります。
Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for numeric types, "" for strings, and nil for pointers, functions, interfaces, slices, channels, and maps.
defer f()
Defer文問題のdefer
文です。
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred.
拙訳: defer文が実行されるごとに、その関数値とその呼出しに対するパラメータは通常通りに評価され、改めて保存されますが、その実際の関数は実行されません。deferされた関数はそれを囲む関数がreturnする直前に、deferされたのと逆順で実行されます。
つまり、
- 関数値とパラメータは
defer
文の実行時に評価される - しかしその関数が実行されるのは
return
の直前
ことがわかります。defer
文の時点では関数が実行されないのですから、少なくともこの時点でpanic
することはありません。
println("HELLO")
これは普通の文で、"HELLO"
を出力します。
defer
文の時点ではpanic
しないので、問題のプログラムは少なくとも"HELLO"
を出力します。
f = func() { recover() }
代入文ここで変数にf
をfunc() { recover() }
という関数を代入しています。
deferされた関数の実行
ここで関数のボディが終わりなので、defer
文で指定した関数が実行されます:
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
この時点では変数f
はfunc() { recover() }
に書き換わっています。しかし、defer
された関数の値はdefer
文の実行時、つまりこのプログラムの2行目の時点で評価されたものが使われます。つまりdefer実行されるのはあくまでもnil
です。
そしてnil
である関数の呼び出しはpanic
を引き起こします。
Calling a nil function value causes a run-time panic.
サマリー
- 変数宣言
var f func()
でf
はnil
で初期化される - その直後の
defer f()
の時点ではf
は実行されないが、f
の値はこの時点での値であるnil
が「保存」される。- 引数を渡した場合も同様
- その後で
f
を書き換えても、defer実行されるf
はnil
のままなので、panic
を引き起こす
よって正解は3.の「HELLOと表示されてpanicする」です。
Discussion