Есть ли foreach в Go? Пошаговый туториал с примерами
Если вы пришли в Go из Python, JavaScript или PHP, то наверняка привыкли к лаконичному foreach для перебора коллекций. И вот вы пишете свой первый цикл на Go и задаётесь вопросом: «А где же мой любимый foreach? Неужели в Go его нет?»
Спойлер: прямого ключевого слова foreach в Go действительно нет. Но есть кое-что гораздо более гибкое и мощное — конструкция for range. В этом туториале мы разберём, как работает итерация в Go, покажем реальные примеры кода и сравним с привычным foreach из других языков.
Почему в Go нет foreach и зачем нужен range?
Go — язык минималистичный. Его создатели намеренно избегали дублирования синтаксических конструкций. Вместо того чтобы добавлять отдельное ключевое слово foreach, они расширили возможности существующего цикла for. Так появился for range — универсальный инструмент для обхода массивов, срезов (slices), карт (maps), строк и даже каналов (channels).
Если вы ищете точный аналог foreach в Go, то вот он — for range.
Синтаксис for range: разбор на пальцах
Базовая структура выглядит так:
for index, value := range collection {
// тело цикла
}
Где:
index— индекс элемента (для массивов и срезов) или ключ (для карт).value— копия значения элемента.collection— ваша коллекция (срез, массив, map, строка или канал).
Важный момент: value — это всегда копия элемента. Если вы измените value внутри цикла, оригинальный элемент не поменяется. Для изменения оригиналов нужно обращаться через индекс.
Пример 1: Обход среза (slice) — аналог foreach в Go
Начнём с самого частого случая — перебор элементов среза:
package main
import "fmt"
func main() {
fruits := []string{"яблоко", "банан", "вишня", "дыня"}
fmt.Println("Перебор среза (аналог foreach):")
for index, fruit := range fruits {
fmt.Printf("Индекс %d: %s\n", index, fruit)
}
}
Вывод:
Перебор среза (аналог foreach):
Индекс 0: яблоко
Индекс 1: банан
Индекс 2: вишня
Индекс 3: дыня
Если вам нужен только индекс (например, для изменения элементов), можно опустить значение:
for i := range fruits {
fruits[i] = "фрукт: " + fruits[i]
}
А если нужны только значения — используйте подчёркивание _:
for _, fruit := range fruits {
fmt.Println(fruit)
}
Пример 2: Обход map — классический foreach
В Go нет более элегантного способа перебора карт, чем for range:
package main
import "fmt"
func main() {
ages := map[string]int{
"Анна": 25,
"Борис": 30,
"Виктор": 35,
}
fmt.Println("Перебор map (foreach):")
for name, age := range ages {
fmt.Printf("%s: %d лет\n", name, age)
}
}
Порядок обхода map в Go не гарантируется. Если вам нужен стабильный порядок, собирайте ключи в срез и сортируйте его.
Пример 3: Обход строки — посимвольная итерация
В отличие от многих языков, for range в Go итерируется по рунам (unicode-символам), а не по байтам:
package main
import "fmt"
func main() {
text := "Привет, Go!"
fmt.Println("Обход строки по символам:")
for i, r := range text {
fmt.Printf("Позиция %d: символ '%c' (код %U)\n", i, r, r)
}
}
Обратите внимание: индекс i указывает на начальный байт руны, а не на порядковый номер символа. Для строк с однобайтовыми символами (ASCII) эти значения совпадают.
Пример 4: Бесконечный цикл и обход канала
for range также работает с каналами. Цикл будет получать значения, пока канал не закроется:
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "сообщение 1"
ch <- "сообщение 2"
close(ch)
}()
fmt.Println("Чтение из канала:")
for msg := range ch {
fmt.Println(msg)
}
}
Это идеальная замена foreach для асинхронной обработки данных.
Сравнение: foreach в Python vs for range в Go
Давайте посмотрим на параллели, чтобы закрепить понимание:
| Задача | Python (foreach) | Go (for range) |
|---|---|---|
| Перебор списка | for item in list: |
for _, item := range slice |
| Перебор с индексами | for i, item in enumerate(list): |
for i, item := range slice |
| Перебор словаря | for key, val in dict.items(): |
for key, val := range map |
| Перебор строки | for char in string: |
for _, r := range string |
Как видите, разница минимальна. Go просто использует более явный синтаксис с указанием переменных для индекса и значения.
Частые ошибки новичков при работе с for range
Вот несколько подводных камней, которые стоит знать:
- Изменение значения не влияет на оригинал. Помните:
value— это копия. Для изменения используйтеcollection[index]. - Захват переменной цикла в горутинах. Если вы передаёте
valueв горутину внутри цикла, всегда делайте копию или передавайте как аргумент:
// НЕПРАВИЛЬНО — все горутины увидят последнее значение
for _, v