Почему вопрос "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 в проекте |