コンテンツにスキップ

Go/defer

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go


Goのdeferキーワードについて

[編集]

Goプログラミング言語におけるdeferキーワードは、関数の実行を遅延させるために使用される重要な機能です。defer文で指定された関数呼び出しは、それを含む関数(外側の関数)がreturnする直前に実行されます。

基本的な使い方

[編集]
defer 関数呼び出し

主な特徴

[編集]
  • 後入れ先出し(LIFO: Last In, First Out): 複数のdefer文がある場合、最後に定義されたものから順に実行される
  • 引数の即時評価: defer文の引数は、defer文が実行された時点で評価される
  • パニックハンドリング: パニックが発生した場合でも、defer文は実行される
  • スコープ: defer文は、それを含む関数内でのみ有効

他のキーワードとの組み合わせとユースケース

[編集]

1. リソース管理との組み合わせ

[編集]
func readFile(filename string) ([]byte, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close() // 関数終了時に必ずファイルを閉じる
    
    return ioutil.ReadAll(file)
}

ユースケース:

  • ファイルハンドルのクローズ
  • ネットワーク接続の終了
  • ロックの解放
  • データベース接続のクローズ

2. recover()との組み合わせ(パニック処理)

[編集]
func safeOperation() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("パニックが発生しました: %v", r)
        }
    }()
    
    // パニックが発生する可能性のある処理
    performRiskyOperation()
    return nil
}

ユースケース:

  • パニックのキャッチと処理
  • グレースフルなエラー回復
  • パニックからのエラー変換

3. 関数引数の即時評価の活用

[編集]
func main() {
    a := 1
    defer fmt.Println("a:", a) // この時点での値(1)が使われる
    
    a = 2
    fmt.Println("関数内のa:", a) // 2を表示
}
// 出力:
// 関数内のa: 2
// a: 1

ユースケース:

  • 関数実行時の状態を記録
  • 変数の元の値の保存
  • デバッグや実行追跡

4. 無名関数との組み合わせ

[編集]
func processData() {
    resource := acquireResource()
    defer func() {
        releaseResource(resource)
        fmt.Println("リソースを解放しました")
    }()
    
    // リソースを使用した処理
}

ユースケース:

  • 複雑なクリーンアップ処理
  • ローカル変数へのアクセス
  • 条件付きの後処理

5. エラー処理との組み合わせ

[編集]
func processWithErrorHandling() (err error) {
    // 最終的なエラー状態を処理
    defer func() {
        if err != nil {
            log.Printf("エラーが発生しました: %v", err)
        }
    }()
    
    if err = step1(); err != nil {
        return err
    }
    
    if err = step2(); err != nil {
        return err
    }
    
    return nil
}

ユースケース:

  • エラーロギング
  • エラー状態の変換や拡張
  • 命名された戻り値の操作

6. トレース/プロファイリングとの組み合わせ

[編集]
func timeTrack(start time.Time, name string) {
    elapsed := time.Since(start)
    log.Printf("%s の実行時間: %s", name, elapsed)
}

func someFunction() {
    defer timeTrack(time.Now(), "someFunction")
    
    // 実行時間を測定したい処理
    time.Sleep(1 * time.Second)
}

ユースケース:

  • 関数の実行時間測定
  • 処理のトレース
  • パフォーマンスプロファイリング

7. ミューテックス/ロックとの組み合わせ

[編集]
func updateSharedResource() {
    mu.Lock()
    defer mu.Unlock() // 関数終了時に確実にロックを解放
    
    // 共有リソースの更新処理
}

ユースケース:

  • 排他制御の簡素化
  • デッドロック防止
  • クリティカルセクションの管理

8. トランザクション管理との組み合わせ

[編集]
func performDatabaseOperation(db *sql.DB) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback() // エラー時は自動的にロールバック
    
    // トランザクション内の操作
    if _, err := tx.Exec("INSERT INTO users (name) VALUES (?)", "John"); err != nil {
        return err
    }
    
    return tx.Commit() // 成功時はコミット
}

ユースケース:

  • データベーストランザクションの管理
  • 失敗時の自動ロールバック
  • ACID特性の確保

9. HTTPリクエスト処理との組み合わせ

[編集]
func handleRequest(w http.ResponseWriter, r *http.Request) {
    // リクエストボディを必ず閉じる
    defer r.Body.Close()
    
    // リクエスト処理
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "リクエストの読み取りに失敗しました", http.StatusBadRequest)
        return
    }
    
    // 処理結果をレスポンスとして返す
    w.Write([]byte("処理完了"))
}

ユースケース:

  • HTTPリクエストの後処理
  • レスポンスヘッダーの設定
  • エラーレスポンスの統一的な処理

10. コンテキスト(context)との組み合わせ

[編集]
func processWithContext(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel() // 関数終了時にキャンセル関数を呼び出す
    
    return doSomethingWithContext(ctx)
}

ユースケース:

  • コンテキストのキャンセル保証
  • リソースリークの防止
  • タイムアウト処理の適切な終了

11. 複数のdeferの順序制御

[編集]
func multipleDefers() {
    fmt.Println("開始")
    
    defer fmt.Println("defer 1") // 最後から2番目に実行
    defer fmt.Println("defer 2") // 最後に実行
    defer fmt.Println("defer 3") // 最初に実行
    
    fmt.Println("処理中")
}
// 出力:
// 開始
// 処理中
// defer 3
// defer 2
// defer 1

ユースケース:

  • 複数のリソースの順序付き解放
  • 入れ子の処理の管理
  • クリーンアップの優先順位付け

12. 値の変更とdeferの組み合わせ

[編集]
func modifyReturnValue() (result int) {
    result = 1
    defer func() { result *= 2 }() // 戻り値を変更
    return 1 // 実際には2が返される
}

ユースケース:

  • 戻り値の後処理
  • 統計情報の集計
  • 最終値の調整

使用上の注意点

[編集]
  1. パフォーマンス: deferは若干のオーバーヘッドを伴うため、頻繁に呼び出される小さな関数内での過剰使用は避ける
  2. 引数評価: defer文の引数は遅延実行ではなく即時評価されることを理解する
  3. 順序: 複数のdeferは逆順に実行されることを考慮する
  4. ループ内での使用: ループ内でdeferを使用すると、関数が終了するまで解放されないため注意が必要
  5. パニック後の実行: パニックが発生してもdefer文は実行されるが、os.Exit()の呼び出し後は実行されない

deferキーワードは、Goにおけるリソース管理とエラー処理のための強力なツールであり、適切に使用することでコードの信頼性と可読性を大きく向上させることができます。