Не могу понять 5.6.1.Предостережение: захват итерационных переменных - PullRequest
0 голосов
/ 25 октября 2018

Я учу The go programming language и не могу понять

 var rmdirs []func()

 for _, dir := range tempDirs() {
     os.MkdirAll(dir, 0755)
     rmdirs = append(rmdirs, func() {
         os.RemoveAll(dir) // NOTE: incorrect!
     })
 }

Я прочитал объяснение в книге несколько раз и до сих пор не могу понять, почему это неправильно?

Я помнючто в го аргументы передаются по значению, поэтому каждый цикл dir является разным значением, почему неверно?

1 Ответ

0 голосов
/ 25 октября 2018

Ваша интуиция верна: go повторно использует один и тот же адрес для значений итерации , поэтому нет гарантии, что значение, на которое указывает dir, когда анонимная функция, добавленная к rmdirs, имеет то же самоезначение, которое оно имело при создании функции и первом захвате dir. Точная формулировка в спецификации :

Переменные итерации могут быть объявлены с помощью предложения range, используя форму краткого объявления переменных (: =).В этом случае их типам присваиваются типы соответствующих значений итерации, а их область действия является блоком оператора for; они повторно используются в каждой итерации .Если переменные итерации объявлены вне оператора for, после выполнения их значения будут такими же, как и в последней итерации.

(выделено мной).Для дальнейшей демонстрации ниже приведена упрощенная версия того, что пытается сделать ваш код:

var rmdirs []func()
tempDirs := []string{"one", "two", "three", "four"}

for _, dir := range tempDirs {
    fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
    rmdirs = append(rmdirs, func() {
        fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
    })
}

fmt.Println("---")

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

При запуске выдается следующий вывод:

dir=one, *dir=0x40e128
dir=two, *dir=0x40e128
dir=three, *dir=0x40e128
dir=four, *dir=0x40e128
---
dir=four, *dir=0x40e128
dir=four, *dir=0x40e128
dir=four, *dir=0x40e128
dir=four, *dir=0x40e128

Ссылка на игровую площадку: https://play.golang.org/p/_rS8Eq9qShM

Как видите, один и тот же адрес повторно используется в каждой итерации цикла.Каждая итерация анонимной функции просматривает один и тот же адрес, поэтому все они выводят одно и то же значение.

Правильный способ обработки подобных ситуаций, как упоминалось в книге, на которую вы ссылаетесь, заключается в определении новой переменнойвнутри цикла, копируя значение итерации в него и передавая его анонимной функции, например, так:

var rmdirs []func()
tempDirs := []string{"one", "two", "three", "four"}

for _, d := range tempDirs {
    dir := d
    fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
    rmdirs = append(rmdirs, func() {
        fmt.Printf("dir=%v, *dir=%p\n", dir, &dir)
    })
}

fmt.Println("---")

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

Это дает ожидаемый результат:

dir=one, *dir=0x40e128
dir=two, *dir=0x40e150
dir=three, *dir=0x40e168
dir=four, *dir=0x40e180
---
dir=one, *dir=0x40e128
dir=two, *dir=0x40e150
dir=three, *dir=0x40e168
dir=four, *dir=0x40e180

Ссылка Playground: https://play.golang.org/p/Ao6fC9i2DsG

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...