Назад
Golang

Циклы в Go

В Go только один вид цикла - for. Но он закрывает все случаи, которые в других языках решаются через while, do-while, foreach.

Базовый for

Классический трёхкомпонентный цикл, как в C/Java.

for init; condition; post {
    // тело
}

Пример:

for i := 0; i < 5; i++ {
    fmt.Println(i) // 0 1 2 3 4
}

for как while

Убираем init и post - получаем аналог while.

n := 1
for n < 100 {
    n *= 2
}
fmt.Println(n) // 128

Бесконечный цикл

for {
    // выполняется вечно
    // выход - только через break или return
}

Практический пример - сервер/воркер:

func worker(jobs <-chan int) {
    for {
        job, ok := <-jobs
        if !ok {
            return // канал закрыт - выходим
        }
        process(job)
    }
}

for range

Итерация по слайсу. Возвращает index, value.

Срез (slice)

nums := []int{10, 20, 30}

for i, v := range nums {
    fmt.Printf("index=%d value=%d\n", i, v)
}
// index=0 value=10
// index=1 value=20
// index=2 value=30

Map

scores := map[string]int{"Alice": 95, "Bob": 87}

for name, score := range scores {
    fmt.Printf("%s: %d\n", name, score)
}

⚠️ Порядок итерации по map не гарантирован.

Строка (string)

for i, r := range "hello" {
    fmt.Printf("%d: %c\n", i, r)
}
// итерация по рунам (unicode), не байтам

Канал (channel)

ch := make(chan int, 3)
ch <- 1; ch <- 2; ch <- 3
close(ch)

for v := range ch {
    fmt.Println(v) // 1 2 3
}
// range по каналу завершится только после close(ch)

Только индекс / только значение

// только индекс
for i := range nums { }

// только значение (индекс игнорируем)
for _, v := range nums { }

Управление циклом

break

Выход из текущего цикла.

for i := 0; i < 10; i++ {
    if i == 5 {
        break
    }
    fmt.Println(i) // 0 1 2 3 4
}

continue

Пропуск текущей итерации.

for i := 0; i < 5; i++ {
    if i%2 == 0 {
        continue
    }
    fmt.Println(i) // 1 3
}

Labels — выход из вложенных циклов

outer:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if j == 1 {
                break outer // выходим из ВНЕШНЕГО цикла
            }
            fmt.Printf("i=%d j=%d\n", i, j)
        }
    }
// i=0 j=0

Подводные камни

1. Захват переменной в горутине

// ❌ Неправильно — все горутины напечатают последнее значение i
for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i)
    }()
}

// ✅ Правильно — передаём i как аргумент
for i := 0; i < 3; i++ {
    go func(i int) {
        fmt.Println(i)
    }(i)
}

Go 1.22+: в новых версиях поведение изменено — переменная цикла создаётся заново на каждой итерации. Но знать про эту проблему всё равно нужно.

2. Изменение слайса во время итерации

// ❌ Не удаляй элементы из слайса в range -  работаешь с копией
nums := []int{1, 2, 3}
for _, v := range nums {
    nums = append(nums, v*10) // бесконечного цикла не будет, но данные будут неожиданными
}

// ✅ Если нужно собрать новый слайс — используй отдельный
var result []int
for _, v := range nums {
    result = append(result, v*10)
}

3. range по map — детерминированность

// Если нужен стабильный порядок — сначала отсортируй ключи
keys := make([]string, 0, len(m))
for k := range m {
    keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
    fmt.Println(k, m[k])
}

Давайте подведем итог

  • В Go один цикл - for, но он покрывает все сценарии
  • for range — основной инструмент для слайсов
  • Следи за захватом переменных в горутинах
  • Labels — редкий, но иногда незаменимый инструмент