В Go ровно одно ключевое слово для циклов: for. У него четыре формы, и вместе они покрывают все циклы, которые в других языках пишут через for, while, do while или foreach.
Заголовок цикла не заключается в круглые скобки, а фигурные скобки обязательны всегда.
Цикл из трёх частей
for init; condition; post {
// body
}
initвыполняется один раз перед циклом. Обычно это короткое объявление переменной вродеi := 0, и эта переменная существует только внутри цикла.conditionпроверяется перед каждой итерацией. Когда оно ложно, цикл заканчивается.- Выполняется тело.
postвыполняется после каждой итерации, обычно этоi++.
В Go i++ это инструкция, а не выражение, поэтому в post нельзя написать i++, j--. Вместо этого используйте параллельное присваивание: i, j = i+1, j-1.
Все три части необязательны. for ; i < 10; {} допустимо, и gofmt перепишет это в for i < 10 {}, то есть в следующую форму.
Цикл только с условием (while в Go)
С одним только условием for ведёт себя как while в других языках:
Отдельного do while нет. Как получить поведение «выполнить хотя бы один раз», показано на странице о цикле while.
Бесконечные циклы
for без ничего после него крутится вечно. Выйти можно через break, return, os.Exit или панику:
for {
line, err := reader.ReadString('\n')
if err != nil {
break
}
process(line)
}
Это стандартная форма для серверов, воркеров и циклов чтения ввода. for {} с пустым телом сжигает ядро процессора; в реальном коде тело блокируется на чём-то (чтение, получение из канала, select).
Циклы range
range обходит коллекцию и на каждой итерации выдаёт одно или два значения. Какие именно, зависит от того, что вы обходите:
| Что обходим | Первое значение | Второе значение |
|---|---|---|
| слайс или массив | индекс | элемент (копия) |
| строка | индекс байта | руна (символ Unicode) |
| мапа | ключ | значение |
| канал | элемент | (нет) |
целое число n (Go 1.22) | от 0 до n минус 1 | (нет) |
| функция-итератор (Go 1.23) | то, что отдаёт функция | то, что отдаёт функция |
Три детали, которые видны в выводе:
- Переменная значения это копия.
v *= 2внутри цикла не изменит слайс; чтобы менять элементы, обращайтесь к слайсу по индексуnums[i], как в третьем цикле. - Индекс строки это смещение в байтах.
éзанимает два байта, поэтому индекс прыгает с 1 на 3. - Порядок обхода мапы случайный намеренно. Go рандомизирует его, чтобы код не мог от него зависеть.
slices.Sorted(maps.Keys(m))(Go 1.23) даёт ключи по порядку.
range полностью, включая каналы, функции-итераторы и правила копирования, разобран на странице про range.
break и continue
break завершает самый внутренний цикл. continue переходит к следующей итерации (в цикле из трёх частей сначала выполняется post):
break и continue с меткой
Обычный break выходит только из самого внутреннего цикла. Чтобы выйти из внешнего цикла изнутри вложенного, пометьте внешний цикл:
Метка это идентификатор с двоеточием, который ставится прямо перед for. Неиспользуемая метка в Go считается ошибкой компиляции, как и неиспользуемая переменная.
break внутри switch и select
Внутри for оператор break в ветке switch или select выходит из switch или select, а не из цикла:
for {
switch cmd := next(); cmd {
case "quit":
break // only exits the switch; the loop keeps running
}
}
Чтобы выйти из цикла изнутри switch или select, используйте break с меткой или return.
Переменные цикла начиная с Go 1.22
Go 1.22 изменил область видимости переменных цикла: теперь каждая итерация получает свежую копию i и v. Раньше одна переменная переиспользовалась на весь цикл, что ломало замыкания и горутины, которые её захватывали:
В Go 1.22 и новее программа печатает 0 1 2 и [aa bb cc]. При старой семантике та же программа, записанная через for i := 0; i < 3; i++ (range по целому числу до 1.22 не существовал), печатала 3 3 3, а горутины устраивали гонку за общие i и name. Старый обходной приём, i := i в начале тела цикла, больше не нужен.
Поведение выбирается для каждого модуля по строке go в go.mod, а не по версии компилятора. Модуль, в котором всё ещё написано go 1.21, сохраняет старую семантику даже при сборке на Go 1.24.
Частые ошибки
Изменение копии. for _, v := range items { v.Count++ } меняет копию. Используйте items[i].Count++ или обходите слайс указателей.
Добавление элементов во время обхода. range вычисляет слайс один раз, в начале. Элементы, добавленные внутри цикла, не обходятся, так что цикл всё равно завершится, но новых элементов не увидит.
Удаление из слайса по индексу в прямом цикле. Удаление элемента i сдвигает остальные влево, и следующий элемент пропускается. Соберите новый слайс, идите в обратную сторону или используйте slices.DeleteFunc.
Ошибка на единицу с <=. for i := 0; i <= len(s); i++ на последнем проходе читает s[len(s)] и паникует с index out of range. Используйте <, а лучше range.
Тяжёлая работа в условии. Условие выполняется на каждой итерации. for i := 0; i < expensiveCount(); i++ вызывает функцию каждый раз; вычислите значение один раз до цикла.
float в качестве счётчика. for x := 0.0; x != 1.0; x += 0.1 никогда не закончится, потому что у 0.1 нет точного двоичного представления. Считайте целым числом и вычисляйте float из него.
Часто задаваемые вопросы
Как написать цикл for в Go?
Классическая форма состоит из трёх частей через точку с запятой и пишется без круглых скобок:
for i := 0; i < 5; i++ {
fmt.Println(i)
}
Начиная с Go 1.22 можно также написать for i := range 5, что считает от 0 до 4. Чтобы пройти по слайсу, используйте for i, v := range items.
Есть ли в Go цикл while?
Ключевого слова while нет, но for с одним только условием и есть цикл while: for n > 0 { n /= 2 }. for { ... } без условия это бесконечный цикл, из которого выходят через break или return.
Как перебрать map в Go?
for key, value := range m { ... }. Порядок обхода не определён, и среда выполнения его рандомизирует, так что он может отличаться от цикла к циклу. Никогда на него не полагайтесь. Для стабильного порядка соберите ключи, отсортируйте их через slices.Sort и пройдите по отсортированным ключам.
Как выйти из вложенного цикла в Go?
Поставьте метку перед внешним циклом и укажите её в break: outer: for ... { for ... { if found { break outer } } }. Обычный break выходит только из самого внутреннего for, switch или select. continue outer аналогично переходит к следующей итерации помеченного цикла.
Что изменилось с переменными цикла в Go 1.22?
До Go 1.22 у цикла for была одна переменная на все итерации, поэтому замыкания и горутины, запущенные в цикле, часто видели последнее значение. Начиная с Go 1.22 каждая итерация получает свою копию, и захват i или v в замыкании работает как ожидается. Новое поведение действует, когда в go.mod модуля указано go 1.22 или новее.