Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

defer

『defer』はGoの制御構文で、関数の終了時に実行される処理を予約します。ファイルのクローズやロックの解放など、確実に実行したい後処理に使用します。

構文
// 関数の終了時に実行する処理を予約します。
defer 関数呼び出し

// 複数のdeferは後入れ先出し(LIFO)順で実行されます。
defer fmt.Println("1番目")  // 3番目に実行されます。
defer fmt.Println("2番目")  // 2番目に実行されます。
defer fmt.Println("3番目")  // 1番目に実行されます。

// 引数はdeferが記述された時点で評価されます。
x := 10
defer fmt.Println(x) // 10が出力されます(後でxが変わっても影響しません)。
x = 20
deferの特徴
特徴概要
LIFO順複数のdeferがある場合、最後に登録されたものが最初に実行されます。
引数の即時評価deferの引数はdeferが記述された時点で評価されます。関数本体は後で実行されます。
名前付き戻り値との連携deferの中から名前付き戻り値を変更できます。関数の最終的な戻り値に影響します。
panic時も実行パニックが発生しても、登録済みのdeferは実行されます。recover()との組み合わせで有用です。
サンプルコード
package main

import "fmt"

// deferによるリソース解放のパターンです。
func readFile(name string) {
    fmt.Printf("ファイル '%s' を開きます\n", name)
    defer fmt.Printf("ファイル '%s' を閉じます\n", name) // 関数終了時に必ず実行されます。

    // ファイル処理の本体(ここではシミュレーション)
    fmt.Println("ファイルを読み込み中...")
    fmt.Println("読み込み完了")
    // 関数がここで終了すると、deferが実行されます。
}

// 複数deferのLIFO実行順を確認します。
func multiDefer() {
    fmt.Println("関数開始")
    defer fmt.Println("defer 1: 最後に実行されます") // 登録1番目 → 実行3番目
    defer fmt.Println("defer 2: 2番目に実行されます") // 登録2番目 → 実行2番目
    defer fmt.Println("defer 3: 最初に実行されます") // 登録3番目 → 実行1番目
    fmt.Println("関数本体の終わり")
}

// deferで名前付き戻り値を変更します。
func divide(a, b float64) (result float64, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("エラーが発生しました: %v", r)
        }
    }()
    result = a / b
    return
}

func main() {
    readFile("data.txt")
    fmt.Println()

    multiDefer()
    fmt.Println()

    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("エラー:", err)
    } else {
        fmt.Printf("10 ÷ 2 = %.1f\n", result)
    }
}
概要

『defer』は関数の終了時に処理を予約するためのキーワードです。ファイルの『f.Close()』やミューテックスの『mu.Unlock()』など、リソースを確実に解放するパターンで広く使われます。複数の『defer』はLIFO(後入れ先出し)順で実行されるため、ネストされたリソースを逆順に解放できます。

ループ内でdeferを使うと、ループが終わるまでdeferが蓄積され、関数終了時に一度に実行されます。ループ内でリソースを即座に解放したい場合は、処理を別関数に切り出してdeferを使うか、deferを使わずに手動で解放してください。

パニック発生時のdeferの動作については『panic() / recover()』も参照してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。