歴史的に、deferの概念はリソースの安全な解放や関数の実行結果に関係なくアクションを完了するためにGoに導入されました(通常の終了やpanicによる終了に関わらず)。しかし、deferとreturn、およびpanicの相互作用には、経験豊富な開発者でさえ見落としがちな不都合な罠がいくつか存在します。
問題は、戻り値の計算順序、名前付き戻り値の動作、defer内でのこれらの値の変更が多くの言語の一般的な動作とは大きく異なることです。さらに、defer内で既に計算された値を変更しようとすると予期しない動作を引き起こす場合があります。
解決策は、常に覚えておくべきです:関数が返す値はdeferが実行される前に計算されますが、名前付き結果を使用している場合、それらはdefer内で関数から実際に戻る前に変更できます。
コードの例:
func tricky() (res int) { defer func() { res = 42 // 戻り値を変更します! }() return 10 } func main() { fmt.Println(tricky()) // 10ではなく42と表示されます }
主な特徴:
defer関数はどのような順序で実行されますか?
それらは宣言された順序の逆(スタック — LIFO)で実行されます。
func f() { defer fmt.Println("1") defer fmt.Println("2") } // 出力: 2、その後1
defer関数のパラメータは、deferを宣言した時点で計算されるのか、それとも実行時に計算されるのか?
defer関数のパラメータは、deferを宣言した時点で計算され、呼び出し時ではありません。
func f() { i := 1 defer fmt.Println(i) // 1が表示されます。たとえiがその後変更されても i = 2 }
deferは無名の結果を変更できますか?
いいえ。deferで変更できるのは名前付き戻り値のみです。
func f() int { defer func() { /* 何も変更しない */ }() return 5 }
若手開発者がdeferを使用して戻り値をログに記録しようとしたが、間違って名前付き戻り値を変更してしまい、正確な関数の結果を"上書き"してしまった。
利点:
欠点:
別の状況では、deferはリソースの解放やログ記録のためだけに使用され、returnを変更せず、重要な値はreturnの前に明示的に設定されました。
利点:
欠点: