Code Fix

上級

deferの引数はdefer文を実行した時点の値で評価される

defer文に渡した関数呼び出しの引数は、実際に関数が実行される(関数の終了時)タイミングではなく、defer文自体が実行された時点で評価され確定します。

なぜエラーが出ないのか

出力: 1
(エラーなし)
Goは何も報告しません。コンパイルも実行も正常に終わるコードだからです
出力: 1
実際の出力 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  package main
 2  
 3  import "fmt"
 4  
 5  func main() {
 6  	x := 1
 7  	defer fmt.Println(x)             
                     ^
 8  	x = 2
 9  }
出力: 1

defer fmt.Println(x)の引数xは、defer文が実行された時点(まだx=1の時点)で評価され確定してしまいます。関数終了時の最新の値を見たいなら、引数のないクロージャをdeferする必要があります。

直し方: defer fmt.Println(x) を defer func() { fmt.Println(x) }() にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  package main
 2  
 3  import "fmt"
 4  
 5  func main() {
 6  	count := 10
 7  	defer fmt.Println(count)             
                       ^
 8  	count = 20
 9  }
出力: 10

deferの引数評価タイミングはdefer文に到達した瞬間なので、その後countをいくら変更しても出力には反映されません。

直し方: defer fmt.Println(count) を defer func() { fmt.Println(count) }() にします。

この問題を解いてみる →

パターン3

 1  package main
 2  
 3  import "fmt"
 4  
 5  func main() {
 6  	n := 5
 7  	defer fmt.Println(n)             
                     ^
 8  	n = 50
 9  }
出力: 5

クロージャでラップすれば、変数nそのものを参照するようになり、関数終了時の最新値が使われます。

直し方: defer fmt.Println(n) を defer func() { fmt.Println(n) }() にします。

この問題を解いてみる →

パターン4

 1  package main
 2  
 3  import "fmt"
 4  
 5  func main() {
 6  	score := 0
 7  	defer fmt.Println(score)             
                       ^
 8  	score = 100
 9  }
出力: 0

deferに直接式の結果を渡す書き方は、その場で評価が完了してしまうため、後から値が変わっても反映されません。

直し方: defer fmt.Println(score) を defer func() { fmt.Println(score) }() にします。

この問題を解いてみる →

パターン5

 1  package main
 2  
 3  import "fmt"
 4  
 5  func main() {
 6  	level := 1
 7  	defer fmt.Println(level)             
                       ^
 8  	level = 9
 9  }
出力: 1

「deferは関数の一番最後の状態を見るはず」という直感は、引数を直接渡す書き方には当てはまりません。

直し方: defer fmt.Println(level) を defer func() { fmt.Println(level) }() にします。

この問題を解いてみる →

よくある誤解

「defer fmt.Println(x)と書けば、関数の一番最後のxの値が出力されるはず」という思い込みは誤りです。引数の評価はdefer文に到達した瞬間に行われるため、その後xの値を変更しても出力には反映されません。値を変更したいなら引数のないクロージャをdeferする必要があります。

実務での勘所

deferの正確な規則は、「関数の呼び出しそのもの」は遅延されますが、「呼び出しに使う引数の値」はdefer文が実行された、その場で確定するというものです。defer fmt.Println(x)と書いた瞬間に、xの現在の値がPrintlnに渡す引数として確定し、後でxを変更してもこの確定した値には影響しません。関数の一番最後の値を出力したい場合は、defer func() { fmt.Println(x) }()のようにクロージャで包み、xという変数自体をキャプチャする必要があります。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)