for range выглядит простой конструкцией, но именно с ним связано множество неочевидных ошибок. Разберём три самые распространённые.
1. Переменная цикла содержит копию элемента
При обходе слайса значение элемента записывается в переменную цикла:
users := []User{
{Name: "Alice"},
{Name: "Bob"},
}
for _, user := range users {
user.Name = strings.ToUpper(user.Name)
}
Исходный слайс не изменится. user — это копия структуры, а не сам элемент.
➡️ Если нужно изменить данные, работайте через индекс:
for i := range users {
users[i].Name = strings.ToUpper(users[i].Name)
}
Для слайса указателей поведение будет другим: копируется указатель, а не объект, на который он указывает.
2. Адрес переменной цикла — не адрес элемента
Такой код встречается довольно часто:
var result []*User
for _, user := range users {
result = append(result, &user)
}
Начиная с Go 1.22 при использовании := каждая итерация получает собственную переменную цикла, поэтому указатели больше не будут ссылаться на одну и ту же переменную, как это происходило раньше.
Но &user всё равно указывает на копию элемента, а не на элемент исходного слайса.
➡️ Если нужны адреса элементов, используйте индекс:
for i := range users {
result = append(result, &users[i])
}
Если же переменная объявлена заранее и используется присваивание =, она по-прежнему переиспользуется между итерациями.
3. Изменение коллекции во время обхода
Для слайсов выражение после range вычисляется один раз — до начала цикла. Поэтому количество итераций определяется исходной длиной слайса.
numbers := []int{1, 2, 3}
for _, n := range numbers {
numbers = append(numbers, n)
}
Добавленные элементы в текущий обход автоматически не попадут. Кроме того, append может выделить новый underlying array, поэтому изменять слайс во время обхода стоит с осторожностью.
➡️ С map правила другие:
for key := range cache {
delete(cache, key)
}
Удалять элементы во время обхода разрешено. А вот элементы, добавленные в процессе итерации, могут как попасть в текущий обход, так и быть пропущены — спецификация этого не гарантирует. Порядок обхода map также не определён.
И помните: одновременно читать и изменять map из разных горутин без синхронизации нельзя.
for range скрывает немало деталей реализации. Если помнить, что переменная цикла часто содержит копию элемента, а поведение зависит от типа коллекции, многих неприятных ошибок удастся избежать.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoToProduction