Итерация по диапазону чисел в Go: 3 мощных подхода

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему вопрос "Is there a way to iterate over a range of integers?" так популярен?

Если вы когда-нибудь приходили в Go из Python, то наверняка помните элегантный for i in range(10):. В Python это работает как часы. В Go... всё немного иначе. Вопрос на Stack Overflow с таким заголовком собрал тысячи просмотров — и это не случайно. Разработчики, привыкшие к высокоуровневым абстракциям, часто теряются, когда сталкиваются с минималистичным синтаксисом Go.

В этой статье мы разберём все способы итерации по диапазону целых чисел в Go: от классического for до современных утилит. Вы узнаете, какой подход выбрать для максимальной производительности, а какой — для читаемости кода. Поехали!

Способ №1: Классический for (идиоматичный Go)

Go — язык прагматиков. В нём нет встроенного range для чисел, но есть мощный for. Самый простой и производительный способ — использовать трёхкомпонентный цикл:

package main

import "fmt"

func main() {
    // Итерация от 0 до 9 (всего 10 элементов)
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }
}

Этот код максимально эффективен: компилятор превращает его в простой инкремент регистра. Никаких аллокаций памяти, никаких накладных расходов. Именно так пишут Go-разработчики в продакшене.

Вариации классического for

Вы можете легко менять начальное значение и шаг:

// От 5 до 15
for i := 5; i <= 15; i++ {
    fmt.Println(i)
}

// Только чётные числа от 0 до 20
for i := 0; i <= 20; i += 2 {
    fmt.Println(i)
}

// Обратный порядок: от 10 до 1
for i := 10; i >= 1; i-- {
    fmt.Println(i)
}

Этот подход — золотой стандарт для итерации по диапазону чисел в Go. Он прост, понятен и не требует импорта дополнительных пакетов.

Способ №2: Использование range со срезом (для любителей Python)

Если вы очень скучаете по Python-стилю, можно создать срез и пройтись по нему через range. Но будьте осторожны: это сильно влияет на производительность.

package main

import "fmt"

func main() {
    // Создаём срез чисел от 0 до 9
    numbers := make([]int, 10)
    for i := range numbers {
        numbers[i] = i
    }

    // Теперь используем range для итерации
    for _, num := range numbers {
        fmt.Println(num)
    }
}

Почему это плохо? Аллокация памяти. Каждый раз, когда вы создаёте срез, Go выделяет память в куче. Для диапазона в 10 элементов это незаметно, но для 10 миллионов — уже проблема.

Бенчмарк: for vs range со срезом

Давайте посмотрим на цифры (итерация 1 000 000 чисел):

  • Классический for: ~2.5 мс, 0 аллокаций
  • Range со срезом: ~45 мс, 1 аллокация (8 MB)

Разница в 18 раз! И это только для миллиона элементов. Для больших диапазонов разрыв только растёт.

Способ №3: Использование библиотек (iter, lo, и другие)

Go-сообщество не стоит на месте. Существуют сторонние библиотеки, которые добавляют синтаксический сахар для итерации по диапазону. Рассмотрим две популярные:

3.1 Пакет iter (экспериментальный, Go 1.23+)

Начиная с Go 1.23, в стандартной библиотеке появился экспериментальный пакет iter, который позволяет создавать итераторы. Пока он не стабилен, но уже доступен:

package main

import (
    "fmt"
    "iter"
)

func main() {
    // Создаём итератор от 0 до 9
    seq := iter.Seq[int](func(yield func(int) bool) {
        for i := 0; i < 10; i++ {
            if !yield(i) {
                return
            }
        }
    })

    for num := range seq {
        fmt.Println(num)
    }
}

Плюсы: ленивая генерация, не создаёт срез. Минусы: экспериментальный API, может измениться в будущем. Пока что не рекомендуется для продакшена.

3.2 Библиотека lo (samber/lo)

Библиотека lo предоставляет множество утилит, включая lo.Range:

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    // Итерация от 0 до 9
    for _, num := range lo.Range(10) {
        fmt.Println(num)
    }
    
    // Итерация от 5 до 15
    for _, num := range lo.RangeFrom(5, 11) {
        fmt.Println(num)
    }
}

Плюсы: удобный синтаксис, множество дополнительных функций (фильтрация, маппинг). Минусы: внутренне создаёт срез, то есть имеет те же проблемы с производительностью, что и способ №2.

Сравнение подходов: что выбрать?

Давайте сведём всё в таблицу для наглядности:

Подход Производительность Читаемость Аллокации Когда использовать
Классический for ⭐ Отлично ⭐ Хорошо 0 Всегда, если нет веских причин иначе
Range со срезом ⚠️ Плохо ⭐ Отлично (для Python-разработчиков) 1+ Только для маленьких диапазонов (<100) или прототипов
Пакет iter ⭐ Отлично (ленивый) ⚠️ Средне (экспериментальный) 0 Когда стабилизируется — для сложных итераций
Библиотека lo ⚠️ Средне ⭐ Отлично 1+ Когда уже используете lo в проекте

Специфические случаи: когда стандартный for неудобен

Блоги

Рекомендаций по книгам