Go の無名関数とクロージャ

Go では関数に名前を付けずに定義できる。これを無名関数(匿名関数)と呼ぶ。無名関数は変数に代入したり、その場で即時実行したりでき、さらに外側のスコープの変数を捕捉する「クロージャ」として振る舞うこともできる。

無名関数の基本

func キーワードに名前を付けず、そのまま関数リテラルとして記述する。変数に代入すれば、あとから何度でも呼び出せる。

func main() {
    add := func(a, b int) int {
        return a + b
    }

    fmt.Println(add(3, 5)) // 8
    fmt.Println(add(1, 2)) // 3
}

変数 add の型は func(int, int) int であり、通常の関数と同じように引数を渡して呼び出せる。Go では関数が第一級オブジェクトとして扱われるため、こうした代入が自然に行える。

即時実行関数

無名関数を定義した直後にカッコを付けて呼び出すと、即時実行される。一度だけ実行したい初期化処理やスコープの分離に便利だ。

func main() {
    result := func(x, y int) int {
        return x * y
    }(4, 7) // 定義直後に呼び出し

    fmt.Println(result) // 28
}

即時実行関数の内部で宣言した変数は外側からアクセスできないため、一時的な処理をカプセル化する用途にも向いている。

クロージャ

無名関数が外側のスコープにある変数を参照すると、その変数への参照を保持したまま動作する。これがクロージャの仕組みだ。

func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

func main() {
    c := counter()
    fmt.Println(c()) // 1
    fmt.Println(c()) // 2
    fmt.Println(c()) // 3
}

counter 関数が返す無名関数は、ローカル変数 count への参照を保持し続ける。counter の実行が終わった後も count はガベージコレクションで回収されず、無名関数が呼ばれるたびにインクリメントされる。

ここで重要なのは、クロージャが変数の「値」ではなく「参照」を捕捉するという点だ。次の例でその違いが明確になる。

func main() {
    funcs := make([]func(), 3)
    for i := 0; i < 3; i++ {
        funcs[i] = func() {
            fmt.Println(i) // i の参照を捕捉
        }
    }
    for _, f := range funcs {
        f() // すべて 3 が出力される
    }
}

ループ変数 i はループ全体で共有されるため、クロージャが実行される時点では i の値は 3 になっている。この問題を回避するには、ループ内で新しい変数を作成して値をコピーすればよい。

func main() {
    funcs := make([]func(), 3)
    for i := 0; i < 3; i++ {
        i := i // 新しい変数にコピー(シャドウイング)
        funcs[i] = func() {
            fmt.Println(i)
        }
    }
    for _, f := range funcs {
        f() // 0, 1, 2 が出力される
    }
}

なお Go 1.22 以降では、for ループの変数が各イテレーションごとに新しくなるよう言語仕様が変更されたため、この問題は自動的に解消される。

クロージャの実用例

クロージャは、設定値を事前に埋め込んだ関数を生成するパターンでよく使われる。

func multiplier(factor int) func(int) int {
    return func(n int) int {
        return n * factor
    }
}

func main() {
    double := multiplier(2)
    triple := multiplier(3)

    fmt.Println(double(5))  // 10
    fmt.Println(triple(5))  // 15
}
通常の関数

呼び出すたびにすべての引数を渡す必要がある

クロージャ

一部の引数を事前に固定し、残りの引数だけで呼び出せる関数を生成できる

このように、クロージャを使えば「設定済みの関数」を量産できる。HTTP ハンドラのミドルウェアや、ソート時のカスタム比較関数など、Go の実践的なコードでは頻繁に登場するパターンだ。無名関数とクロージャは、Go の関数型プログラミング的な側面を支える重要な機能といえる。