Avatar ·

Изменение элементов слайса во время итерации в Go: почему не работает и как исправить?

Всем привет! Я только начал изучать Go и столкнулся с непонятным поведением при итерации по слайсу. У меня есть простой код, где я хочу удвоить каждый элемент в слайсе целых чисел: ```go package main import "fmt" func main() { nums := []int{1, 2, 3, 4, 5} for _, val := range nums { val = val * 2 } fmt.Println(nums) // Ожидаю: [2, 4, 6, 8, 10], но получаю: [1, 2, 3, 4, 5] } ``` Я ожидал, что после цикла значения в слайсе изменятся, но они остались прежними. Я проверил — компилятор не выдаёт ошибок, код запускается, но результат не тот. Почему так происходит? Я думал, что `val` — это ссылка на элемент слайса, но похоже, что это не так. Как правильно изменять элементы слайса во время итерации? Есть ли другие способы, кроме как использовать индекс? Заранее спасибо за помощь! <<>>

Проблема: копирование значения при итерации

В 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 нет встроенной функции для этого — приходится писать свою. Однако он более безопасен и читаем для сложных преобразований.