go 閉包捕獲問題

Undefined443發表於2024-06-08

在 Go 語言中,閉包(closure)是一個函式值,它引用了其外部作用域中的變數。簡而言之,閉包能夠“捕獲”並“記住”其外部作用域中的變數,即使這個變數的生命週期已經結束。閉包的這種特性使得它在許多程式設計場景中非常有用,但也可能導致一些意外行為,尤其是在捕獲變數時。

捕獲問題的例子

一個常見的捕獲問題是迴圈變數的捕獲。下面是一個經典的例子:

package main

import "fmt"

func main() {
    var funcs []func()

    for i := 0; i < 3; i++ {
        funcs = append(funcs, func() {
            fmt.Println(i)
        })
    }

    for _, f := range funcs {
        f()
    }
}

你可能期望這個程式輸出 0, 1, 2,但實際上它會輸出 3, 3, 3。原因在於閉包捕獲的是變數 i 的引用,而不是它的值。迴圈結束後,i 的最終值是 3,因此所有的閉包都引用的是這個最終值。

解決方法

要解決這個問題,可以透過在每次迭代中建立一個新的變數來捕獲當前的值:

package main

import "fmt"

func main() {
    var funcs []func()

    for i := 0; i < 3; i++ {
        i := i // 重新宣告一個新的 `i` 變數
        funcs = append(funcs, func() {
            fmt.Println(i)
        })
    }

    for _, f := range funcs {
        f()
    }
}

或者使用一個輔助函式:

package main

import "fmt"

func main() {
    var funcs []func()

    for i := 0; i < 3; i++ {
        funcs = append(funcs, createFunc(i))
    }

    for _, f := range funcs {
        f()
    }
}

func createFunc(i int) func() {
    return func() {
        fmt.Println(i)
    }
}

在這兩個示例中,每個閉包都捕獲了不同的 i 值,這樣就能得到期望的輸出 0, 1, 2

總結

閉包捕獲變數時,容易出現捕獲變數引用而不是值的問題。特別是在迴圈中,這種問題更容易發生。透過在每次迭代中建立一個新的變數或者使用輔助函式,可以有效地避免這種問題。理解閉包的捕獲機制對於編寫正確且高效的 Go 程式碼非常重要。

相關文章