Изменение элементов слайса во время итерации в Go: почему не работает и как исправить?
Проблема: копирование значения при итерации
В Go при использовании конструкции for range переменная val получает копию каждого элемента слайса, а не ссылку на него. Поэтому все изменения, которые вы делаете с val, влияют только на локальную копию, а исходный слайс остаётся нетронутым.
В вашем коде:
for _, val := range nums {
val = val * 2 // меняется копия, nums не изменяется
}
Решение 1: использование индекса (рекомендуемый способ)
Самый простой и понятный способ — обращаться к элементу по индексу:
for i := range nums {
nums[i] = nums[i] * 2
}
Или, если вам нужно и значение:
for i, val := range nums {
nums[i] = val * 2
}
Решение 2: работа с указателями
Если вы хотите модифицировать элементы через указатели, можно использовать слайс указателей (но это редко нужно):
nums := []int{1, 2, 3, 4, 5}
ptrs := make([]*int, len(nums))
for i := range nums {
ptrs[i] = &nums[i]
}
for _, p := range ptrs {
*p *= 2
}
Решение 3: использование функции с замыканием
Можно передавать указатель на элемент в функцию:
func double(n *int) {
*n *= 2
}
for i := range nums {
double(&nums[i])
}
Важные замечания
- Для массивов (не слайсов) поведение такое же —
for rangeсоздаёт копии элементов. - Для карт (map) и каналов
for rangeтоже работает с копиями ключей/значений, но модификация значений в карте возможна напрямую через ключ. - Если вы работаете со структурами, изменение полей через копию тоже не сработает — нужно использовать индекс.
Почему так сделано?
Это дизайнерское решение Go: for range всегда возвращает копии, чтобы избежать случайных мутаций и сделать код более предсказуемым. Это особенно важно при конкурентном доступе.
// Пример с картой (map) — здесь изменение работает, потому что map хранит ссылки
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
m[k] = v * 2 // работает, но v — всё ещё копия
}
Итог
Всегда используйте индекс для изменения элементов слайса или массива во время итерации. Это идиоматично для Go и не вызывает неожиданностей.
<<Альтернативный подход: модификация через срезы (slicing tricks)
Если вам нужно применить одинаковое преобразование ко всем элементам, можно использовать вспомогательную функцию, которая создаёт новый слайс:
func mapInts(nums []int, fn func(int) int) []int {
result := make([]int, len(nums))
for i, v := range nums {
result[i] = fn(v)
}
return result
}
// Использование:
nums := []int{1, 2, 3, 4, 5}
nums = mapInts(nums, func(n int) int { return n * 2 })
fmt.Println(nums) // [2, 4, 6, 8, 10]
Когда это полезно?
- Когда вы не хотите изменять исходный слайс (функциональный стиль)
- Когда логика преобразования сложная и её нужно переиспользовать
- При работе с неизменяемыми данными (например, в конкурентных сценариях)
Этот подход напоминает map() из других языков, но в Go нет встроенной функции для этого — приходится писать свою. Однако он более безопасен и читаем для сложных преобразований.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги