初めてのGo言語を読んでdefer
の挙動が気になったので、確認した結果のメモ。
実行順序
実行タイミング
func main() {
fmt.Println("start")
defer fmt.Println(1)
fmt.Println("end")
}
出力
start
end
1
defer
を除く処理が完了した後、defer
実行。
defer文を複数書いた場合
func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
}
出力
3
2
1
宣言順とは逆順で実行。
関数がreturn
する関数をdefer
指定した場合
func main() {
fmt.Println("main start")
// returnする関数をdefer指定 (関数後ろが()())
defer func() func() {
fmt.Println("func1")
return func() {
fmt.Println("return1")
}
}()()
// 外側?の関数をdefer指定 (関数後ろが())
defer func() func() {
fmt.Println("func2")
return func() {
fmt.Println("return2")
}
}()
defer fmt.Println("noname func")
fmt.Println("main end")
}
出力
main start
func1
main end
noname func
func2
return1
関数が返す関数をdefer
指定(1つ目のdefer
)した場合、外側?の関数のreturn
まではmain
の流れの通りに実行される。そこで返された関数の実行は、普通のdefer
と同様の順序で行われる。
また、2つ目のdefer
指定した関数が返す関数は実行されない。
戻り値への影響
名前付き戻り値を使わない場合
// returnした変数と同名の変数を変更
func func1() string {
result := "func1"
defer func() {
fmt.Println("defer実行")
result = "changed"
}()
return result
}
// defer指定した関数からstringをreturn
func func2() string {
defer func() string {
return "changed"
}()
return "func2"
}
func main() {
fmt.Println(func1())
fmt.Println(func2())
}
出力
defer実行
func1
func2
defer
も実行はされるが、defer
指定した関数が行ったresult
の変更は戻り値に反映されない。
また、defer
指定した関数からreturn
したとしても、戻り値は変わらない。
名前付き戻り値と違う変数名の変数をreturnした場合
func func1() (s string) {
s = "start"
result := "result"
defer func() {
fmt.Println(s)
s = "changed"
}()
return result
}
func main() {
fmt.Println(func1())
}
出力
result
changed
return
する変数名に関わらず、名前付き戻り値として指定した変数名で戻り値の参照・変更が可能。
変数の評価タイミング
defer
指定した関数に引数として与えない場合
func main() {
result := "start"
defer func() {
fmt.Println(result)
}()
result = "end"
}
出力
end
main
関数終了時に変数を評価。
defer
指定した関数に引数として与える場合
func main() {
result := "start"
defer func(result string) {
fmt.Println(result)
}(result)
result = "end"
}
出力
start
defer
通過時に変数を評価。
その他
os.Exit()
で関数を終了した場合
func main() {
defer fmt.Println(1)
fmt.Println("end")
os.Exit(0)
}
出力
end
deferは実行されない。