Цикл for ... range позволяет удобно проходить по элементам среза (slice), карты (map), массива или строки.
Пример:
s := []int{1, 2, 3} for i, v := range s { fmt.Println(i, v) } m := map[string]int{"a":1, "b":2} for k, v := range m { fmt.Println(k, v) }
Ключевая тонкость:
Переменные i, v, k и т.д. переиспользуются на всех итерациях! Это часто становится источником багов при передаче их по ссылке внутри цикла или запуске goroutine внутри range.
Что произойдет, если внутри range по slice запускать горутину, захватывая переменную итерации? Как избежать ошибки?
Ответ: Возникает типичная ошибка: внутри goroutine используются переменные цикла, которые после завершения цикла будут иметь последнее значение. Чтобы избежать — нужно создавать локальные копии:
nums := []int{1, 2, 3} for _, v := range nums { go func(val int) { fmt.Println(val) }(v) }
История
В одном проекте использовали range для заполнения slice через несколько goroutine, забыв сделать копии переменных цикла. Все горутины напечатали одинаковое значение — последнее из массива, что сильно испортило бизнес-логику.
История
При range по map ссылку на значение сохраняли в новый срез указателей. В результате все элементы нового среза ссылались на одну и ту же переменную — ту, которая использовалась в цикле (копия значения). Баг проявился при обновлении этих переменных вне цикла (panic: invalid memory address или неожиданные данные).
История
Во внутреннем инструменте range по string запускал обработчики весомых подстрок, но для каждой итерации получал смещение в байтах, а не символах Unicode. Итог: некорректная обработка для строк Юникода, некорректное разрезание символов.