Работа со значениями в цикле в go

Я только изучаю golang, перехожу на него с java, и поведение в go цикла после java оказалось для меня немного неожиданным. Помогите, пожалуйста, разобраться, мне кажется, я запуталась. Почему в случае

    funcs := []func(){}

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

    funcs[0]()

получается 5, а не 0, а в случае

    ids := []int{}
    for i := 0; i < 5; i++ {
        ids = append(ids, i)
    }

    fmt.Println(ids)

получается [0 1 2 3 4]?

Что будет, если у меня есть структура

type task struct {
id string
}

Что будет в слайсе result, если использовать его дальше? Только последний task1 или все, как в tasks?

func test(tasks []task ) []task{
var result = make([]task, 0)
 for _, task1 = range tasks  {
    result = append(result , task1 )
 }
return result
}

Ответы (1 шт):

Автор решения: Bearded Beaver

Это достаточно известная ловушка языка Go, только она не в циклах, а в замыканиях. В первом примере у вас создается слайс, в него помещаются замыкания (функции), каждое из которых захватывает переменную из счетчика цикла. Здесь кроется нюанс - в Go до версии 1.22 переменная в замыкание захватывается по ссылке, то есть все элементы слайса захватили одно и то же значение.

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

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

Теперь на каждой итерации цикла замыкание захватывает свою локальную переменную и они никак не связаны.

Циклы же работают без сюрпризов, как в остальных ваших примерах.

Эта милая особенность исправлена начиная с версии 1.22, который на момент написания ответа еще не вышел. Подробнее про это поведение и изменения в новой версии:

→ Ссылка