Go言語のdefer文がどのように動作するかを解説する技術連載の第1回です。defer文は実行時(到達した時点)で関数値と引数がメモリに保存され、実際の呼び出しは関数終了時まで遅延されます。引数は登録時の値で評価・固定されるため、ループ変数を使う例ではその時点の値で固定されるとのことです。値レシーバのメソッドをdeferする場合は構造体のコピーが保存され、ポインタレシーバの場合はアドレスが保存される違いも解説されています。

Goのdeferには3つの内部実装方式があるとされています。heap-allocated方式では_defer構造体をヒープに確保してgoroutineの持つリストに繋ぎ、stack-allocated方式では同じ構造体を関数フレーム内に配置します。open-coded方式では_defer構造体自体を作らず、コンパイラが後始末処理を関数の出口に直接コード展開します。open-coded defersは1バイトのビットマスクで管理されるため、1つの関数内に9個以上のdefer文があるとstack-allocated方式に切り替わるとのことです。複数のdefer文はLIFO(後入れ先出し)の順序で実行されます。

出典が伝えている要点

  • defer文は実行時(到達した時点)で関数値と引数がメモリに保存され、実際の呼び出しは関数終了時まで遅延される
  • defer文の引数は登録時(defer行を通過した時点)の値で評価・固定される(例: ループ変数iの値0で固定される例が挙げられている)
  • defer文で保存される関数値は登録時に確定し、後で変数の指す関数を差し替えても影響を受けない
  • 値レシーバのメソッドをdeferする場合は構造体のコピーが保存され、ポインタレシーバの場合はアドレスが保存される
  • Goのdeferには3つの実装方式が存在する: heap-allocated、stack-allocated、open-coded
  • heap-allocated方式では_defer構造体をヒープに確保し、goroutineの持つリストに繋ぐ
  • stack-allocated方式では_defer構造体を関数フレーム内に配置し、同じくリストに繋ぐ
  • open-coded方式では_defer構造体を作らず、コンパイラが後始末処理を関数の出口に直接コード展開する
  • _defer構造体はspフィールドにスタックポインタ位置の情報を保持し、これによりどの関数呼び出し層の後始末かを判別する
  • open-coded defersは1バイト(8ビット)のビットマスクで管理されるため、1つの関数内に9個以上のdefer文があるとstack-allocated方式に切り替わる
  • 複数のdefer文はLIFO(後入れ先出し)の順序で実行される

原文より

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.

出典: zenn.dev

← 2026.09.15 の号を通しで読む