Menu

Цикл for в Golang: все четыре формы с примерами

В Go одно ключевое слово для циклов, for, и оно покрывает всё: счётный цикл из трёх частей, цикл только с условием (аналог while), бесконечный цикл и range по слайсам, мапам, строкам, каналам, целым числам и функциям. А также break и continue с меткой и изменение переменных цикла в Go 1.22.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

В Go ровно одно ключевое слово для циклов: for. У него четыре формы, и вместе они покрывают все циклы, которые в других языках пишут через for, while, do while или foreach.

Заголовок цикла не заключается в круглые скобки, а фигурные скобки обязательны всегда.

Цикл из трёх частей

for init; condition; post {
	// body
}
  1. init выполняется один раз перед циклом. Обычно это короткое объявление переменной вроде i := 0, и эта переменная существует только внутри цикла.
  2. condition проверяется перед каждой итерацией. Когда оно ложно, цикл заканчивается.
  3. Выполняется тело.
  4. 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 или новее.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ