Почему не срабатывает замыкание?

В Go, замыкание - это функция, которая имеет доступ к переменным из объемлющей ее области видимости, даже после того, как эта область видимости завершена.

Однако, чтобы понять, почему замыкание не работает в какой-то конкретной ситуации, нужно рассмотреть код и исследовать причины его неправильного поведения. В данном ответе мы рассмотрим несколько основных причин, почему замыкание может не срабатывать в Go.

1. Ошибка с обновлением переменной в цикле.
При использовании замыкания в цикле, переменная, используемая в замыкании, может быть не уникальной для каждой итерации. Это происходит из-за того, что переменная в цикле обновляется на каждой итерации, и замыкание ссылается на последнее значение этой переменной после завершения цикла. Чтобы избежать этой проблемы, можно создать временную переменную внутри цикла, чтобы переменная в замыкании была уникальной для каждой итерации.

Например, рассмотрим следующий код:

func main() {
    var funcs []func()
    for i := 0; i < 5; i++ {
        funcs = append(funcs, func() {
            fmt.Println(i)
        })
    }
    for _, f := range funcs {
        f()
    }
}

Ожидается, что вывод будет 0 1 2 3 4, но на самом деле будет 5 5 5 5 5.

Чтобы исправить это, можно внести переменную i внутрь замыкания, чтобы оно получало уникальное значение i для каждой итерации:

func main() {
    var funcs []func()
    for i := 0; i < 5; i++ {
        iCopy := i
        funcs = append(funcs, func() {
            fmt.Println(iCopy)
        })
    }
    for _, f := range funcs {
        f()
    }
}

Теперь вывод будет ожидаемым: 0 1 2 3 4.

2. Использование указателей на переменные.
В Go замыкания также могут быть связаны с указательными переменными. Однако, если значение переменной, на которую ссылается указатель, изменяется до вызова замыкания, то замыкание будет ссылаться на новое значение.

Рассмотрим следующий пример:

func main() {
    var f func()
    var x int
    f = func() {
        fmt.Println(x)
    }
    x = 42
    f()
}

Ожидается, что вывод будет 42, но на самом деле будет 0.

Это происходит потому, что замыкание f ссылается на переменную x по указателю, который хранит значение 0 на момент присваивания f = func() { fmt.Println(x) }. Изменение значения x позднее не влияет на значение, на которое ссылаются замыкание.

Чтобы исправить это, можно передать указатели на переменные в замыкание напрямую:

func main() {
    var f func()
    var x int
    x = 42
    f = func() {
        fmt.Println(x)
    }
    f()
}

Теперь вывод будет ожидаемым: 42.

Это только некоторые из возможных причин, почему замыкание может не срабатывать в Go. Важно внимательно анализировать код и понимать, как работает замыкание в конкретной ситуации, чтобы не столкнуться с такими проблемами.