ПрограммированиеGo backend engineer

Поясните, как устроен механизм range в Go при работе с map и slice. В чем тонкости использования переменных цикла и с какими ошибками можно столкнуться?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Цикл for ... range позволяет удобно проходить по элементам среза (slice), карты (map), массива или строки.

  • Для 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. Итог: некорректная обработка для строк Юникода, некорректное разрезание символов.