ちゃっくのメモ帳

ちゃっくがメモしときたいことをメモしとくよ

今日はGoのdeferでバグらせた

Goには実行を遅延させるdeferキーワードがある。
これを使ってバグらせた。

例として次のようなコードを書いたとする。
このコードを実行すると、invalid memory address or nil pointer dereferenceでpanicする。
これくらいシンプルなコードだとわかりやすいが、panicのエラーはreturn文のところを指すので、もう少し複雑なコードになるとちょっとエラーを追跡しにくい.....

func f() string {
	var g func()
	defer g()

	g = func() {
		println("Hello")
	}

	return "World"
}

func main() {
	println(f())
}

Tour of Goのdeferの説明には次のように書いてある。

The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.

これを、「引数の評価は即座に行われ、関数の呼び出しはreturn時に行われる」という解釈を誤っていた。
呼び出す関数の決定も実行時に遅延されると間違えていた。

Goのreference manualを見ると次のように書いてある。
The Go Programming Language Specification - The Go Programming Language

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.

If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the "defer" statement is executed.

実際はdefer statementが記述された地点で呼び出す関数の決定も行われ、実行だけが遅延されるらしい。

次のようなコードを書くとその挙動を確認できると思う。

func f() string {
	var g func()

	g = func() {
		println("Hello1")
	}

	defer g()

	g = func() {
		println("Hello2")
	}

	return "World"
}

func main() {
	println(f())
}
// Hello1
// World
// と出力される

まぁ、後から振り返ってみると遅延して実行される関数がdefer statementとして記述された時点と違う実装が呼び出されたら、もうその関数を遅延して実行したとは言い難い気がするし、かなり扱いにくいものになっていただろうからこの挙動は納得がいく。

お仕事終わった後の趣味プログラミングくらいバグらせないでスカッと書きたい.....