Deferは、現在の関数が終了した後に関数を実行できるGoのユニークなメカニズムで、panicが発生してもそうです。歴史的には、on-exit構造の類似物ですが、Goではdeferred呼び出しのスタックとして実装されています。重要なポイントは、deferred関数のパラメータはdeferの宣言時に直ちに計算され、実際の呼び出し時には計算されないということです!
問題 — 明白ではない振る舞い: deferが発動する時にパラメータが渡されると期待するかもしれませんが、実際にはそうではありません。これは、可変な変数や外部変数に対処する際にバグを引き起こすことがよくあります。
解決策 — パラメータは直ちに計算され、deferの効果がその後発生することを常に考慮してください。
コードの例:
func f() { x := 5 defer fmt.Println(x) x = 10 } // 出力: 5, ではなく10
主な特徴:
次のコードは何を出力しますか?なぜ?
func main() { i := 0 defer fmt.Println(i) i = 1 }
答え: 0が出力されます。fmt.Printlnの引数はdeferの宣言時に保存されます。
deferの宣言後に変数の変更は、その値が関数に渡されることに影響しますか?
いいえ、影響しません — 計算はdeferの宣言時に行われます:
defer fmt.Println(x) // 値xは今保存され、後ではありません
deferを使って変数の最終的な状態を出力できますか?
はい、無名関数(クロージャ)を使えばできます:
defer func() { fmt.Println(x) }() // deferredの呼び出し時にxの現在の値をキャッチします
コードが次のように呼び出されます:
var f *os.File // ... defer f.Close()
しかし、fは後で割り当てられるので、nilポインタによるpanicが発生します!
利点:
欠点:
無名のdeferred関数を使用して清掃処理をラップし、確認を行います:
defer func() { if f != nil { f.Close() } }()
利点:
欠点: