Паттерны итераторов Go 1.24: 7 продакшен-паттернов от Range Over Func до конвейеров данных

编程语言(Обновлено 14 июл. 2026 г.)

Когда циклы for встречаются с функциями: смена парадигмы итераторов в Go

На прошлой неделе я рефакторил сервис обработки данных с 3 уровнями вложенных циклов for, обрабатывающих 100K записей — память подскочила до 2 ГБ, потому что каждый уровень должен был хранить промежуточные результаты в срезах перед передачей на следующий. После перехода на конвейер итераторов память снизилась до 15 МБ, а обработка стала на 30% быстрее. Ключевой сдвиг: больше никакого «собери, затем обработай» — вместо этого «итерируй и обрабатывай на лету».

Go 1.24 официально стабилизировал синтаксис range over func и пакет iter, предоставив Go нативные итераторы с нулевым выделением памяти и возможностью композиции. Это не синтаксический сахар — это фундаментальный сдвиг в парадигме обработки данных. В этой статье рассматриваются 7 продакшен-паттернов итераторов Go, которые помогут вам создавать эффективные, элегантные, компонуемые конвейеры данных.


Ключевые выводы

  • range over func — основной синтаксис итераторов: Go 1.24 позволяет напрямую итерировать по функциям
  • Преобразование Push/Pull итераторов: Поймите оба направления, освойте преобразование iter.Pull
  • Композиция конвейеров данных: Цепочки Map/Filter/Reduce с нулевым промежуточным выделением памяти
  • Ленивые вычисления и бесконечные последовательности: Вычисления по требованию, обработка бесконечных потоков данных
  • Параллельные итераторы и Fan-out: Несколько горутин, параллельно потребляющих итераторы
  • Обработка ошибок в итераторах: Изящная обработка ошибок во время итерации
  • Проектирование продакшен-библиотеки итераторов: Создание переиспользуемых наборов инструментов итераторов

Содержание

  1. Справочник по основным концепциям итераторов Go
  2. Паттерн 1: Базовый итератор range over func
  3. Паттерн 2: Преобразование Push/Pull итераторов
  4. Паттерн 3: Композиция конвейеров данных (Map/Filter/Reduce)
  5. Паттерн 4: Ленивые вычисления и бесконечные последовательности
  6. Паттерн 5: Параллельные итераторы и Fan-out
  7. Паттерн 6: Обработка ошибок в итераторах
  8. Паттерн 7: Проектирование продакшен-библиотеки итераторов
  9. 5 распространённых ошибок и решения
  10. 10 распространённых ошибок и способы их устранения
  11. Продвинутые советы по оптимизации
  12. Сравнительный анализ: Итераторы vs Каналы vs Срезы
  13. Рекомендуемые онлайн-инструменты
  14. Итоги

Справочник по основным концепциям итераторов Go

Концепция Сигнатура Назначение Пример
Функция-итератор func(yield func(V) bool) Итератор с одним значением func(yield func(int) bool)
Итератор ключ-значение func(yield func(K, V) bool) Итерация по парам ключ-значение func(yield func(int, string) bool)
Pull-итератор func() (V, bool) Извлечение по требованию next, stop := iter.Pull(seq)
iter.Pull func(Seq[V]) (func() (V, bool), func()) Преобразование Push в Pull Обход, управляемый потребителем
iter.Stop Встроенная функция остановки Раннее завершение stop() освобождает ресурсы
Возвращаемое значение yield bool Управление продолжением/остановкой итерации yield(v) возвращает false для остановки
Ленивые вычисления Отложенные вычисления Генерация значений по требованию Бесконечные последовательности, строки файла
Композиция конвейеров Цепочки функций Нулевое промежуточное выделение памяти Filter(Map(Seq, fn), pred)

Паттерн 1: Базовый итератор range over func

Проблема: Ловушки памяти при традиционном обходе

func GetAllUsers(db *sql.DB) ([]User, error) {
    rows, err := db.Query("SELECT id, name, email FROM users")
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var users []User
    for rows.Next() {
        var u User
        if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
            return nil, err
        }
        users = append(users, u)
    }
    return users, rows.Err()
}

1 миллион пользователей? 1 миллион структур User загружается в память. Вам нужны только первые 10? Очень жаль — сначала загрузите их все.

Решение: Итератор range over func

package iterator

import (
    "database/sql"
    "iter"
)

type User struct {
    ID    int
    Name  string
    Email string
}

func AllUsers(db *sql.DB) iter.Seq2[int, User] {
    return func(yield func(int, User) bool) {
        rows, err := db.Query("SELECT id, name, email FROM users")
        if err != nil {
            return
        }
        defer rows.Close()

        i := 0
        for rows.Next() {
            var u User
            if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
                return
            }
            if !yield(i, u) {
                return
            }
            i++
        }
    }
}

Использование:

for i, user := range AllUsers(db) {
    fmt.Printf("%d: %s\n", i, user.Name)
    if i >= 9 {
        break
    }
}

Когда выполняется break, yield возвращает false, и функция-итератор немедленно завершается — запрошено только 10 строк, соединение с базой данных корректно закрыто.

Поток выполнения итератора

┌─────────────┐     yield(v)      ┌──────────────┐
│  Iterator    │ ──────────────→  │   range loop  │
│  (producer)  │                   │  (consumer)  │
│              │  ←──────────────  │              │
│              │   yield returns   │              │
│              │      bool         │              │
└─────────────┘                   └──────────────┘
      │                                  │
      │  yield returns false → return    │
      │  (early termination)             │
      └──────────────────────────────────┘

Итератор с одним значением vs итератор ключ-значение

type IntSlice []int

func (s IntSlice) Values() iter.Seq[int] {
    return func(yield func(int) bool) {
        for _, v := range s {
            if !yield(v) {
                return
            }
        }
    }
}

func (s IntSlice) All() iter.Seq2[int, int] {
    return func(yield func(int, int) bool) {
        for i, v := range s {
            if !yield(i, v) {
                return
            }
        }
    }
}
nums := IntSlice{10, 20, 30}

for v := range nums.Values() {
    fmt.Println(v)
}

for i, v := range nums.All() {
    fmt.Printf("index=%d value=%d\n", i, v)
}

Паттерн 2: Преобразование Push/Pull итераторов

Различия между Push и Pull итераторами

Push Iterator (iter.Seq)            Pull Iterator (func() (V, bool))
┌──────────────────┐               ┌──────────────────┐
│  Producer pushes  │               │  Consumer pulls   │
│  yield(v) → cons. │               │  next() → prod.   │
│                    │               │                    │
│  Good for: range   │               │  Good for: manual  │
│  Good for: pipes   │               │  Good for: peek    │
│  Good for: lazy    │               │  Good for: interop │
└──────────────────┘               └──────────────────┘
         │                                  │
         │      iter.Pull() conversion      │
         └──────────────────────────────────┘

Использование преобразования iter.Pull

package main

import (
    "fmt"
    "iter"
)

func Countdown(n int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := n; i > 0; i-- {
            if !yield(i) {
                return
            }
        }
    }
}

func main() {
    next, stop := iter.Pull(Countdown(5))
    defer stop()

    for {
        v, ok := next()
        if !ok {
            break
        }
        fmt.Println(v)
        if v == 3 {
            fmt.Println("Early termination")
            break
        }
    }
}

Практические применения Pull-итератора: Peek и Take

package iterutil

import "iter"

func Take[V any](seq iter.Seq[V], n int) iter.Seq[V] {
    return func(yield func(V) bool) {
        count := 0
        for v := range seq {
            if count >= n {
                return
            }
            if !yield(v) {
                return
            }
            count++
        }
    }
}

func First[V any](seq iter.Seq[V]) (V, bool) {
    next, stop := iter.Pull(seq)
    defer stop()
    return next()
}

func PeekN[V any](seq iter.Seq[V], n int) []V {
    result := make([]V, 0, n)
    next, stop := iter.Pull(seq)
    defer stop()

    for i := 0; i < n; i++ {
        v, ok := next()
        if !ok {
            break
        }
        result = append(result, v)
    }
    return result
}
nums := Countdown(100)
fmt.Println(First(nums))
fmt.Println(PeekN(nums, 5))

Важно: Очистка ресурсов iter.Pull

func ProcessLines(filename string) iter.Seq[string] {
    return func(yield func(string) bool) {
        file, err := os.Open(filename)
        if err != nil {
            return
        }
        defer file.Close()

        scanner := bufio.NewScanner(file)
        for scanner.Scan() {
            if !yield(scanner.Text()) {
                return
            }
        }
    }
}

func main() {
    next, stop := iter.Pull(ProcessLines("huge.log"))
    defer stop()

    v, ok := next()
    if ok {
        fmt.Println("First line:", v)
    }
}

defer stop() гарантирует, что файл будет корректно закрыт, даже если получено только одно значение.


Паттерн 3: Композиция конвейеров данных (Map/Filter/Reduce)

Проблема: Вложенные циклы и промежуточные срезы

func ProcessOrders(orders []Order) float64 {
    var active []Order
    for _, o := range orders {
        if o.Status == "active" {
            active = append(active, o)
        }
    }

    var amounts []float64
    for _, o := range active {
        amounts = append(amounts, o.Amount*1.1)
    }

    var total float64
    for _, a := range amounts {
        total += a
    }
    return total
}

3 обхода, 2 промежуточных среза. При больших объёмах данных нагрузка на память и GC значительна.

Решение: Конвейер итераторов

package pipeline

import "iter"

func Map[V any, U any](seq iter.Seq[V], fn func(V) U) iter.Seq[U] {
    return func(yield func(U) bool) {
        for v := range seq {
            if !yield(fn(v)) {
                return
            }
        }
    }
}

func Map2[K any, V any, U any](seq iter.Seq2[K, V], fn func(K, V) U) iter.Seq[U] {
    return func(yield func(U) bool) {
        for k, v := range seq {
            if !yield(fn(k, v)) {
                return
            }
        }
    }
}

func Filter[V any](seq iter.Seq[V], pred func(V) bool) iter.Seq[V] {
    return func(yield func(V) bool) {
        for v := range seq {
            if pred(v) {
                if !yield(v) {
                    return
                }
            }
        }
    }
}

func Filter2[K any, V any](seq iter.Seq2[K, V], pred func(K, V) bool) iter.Seq2[K, V] {
    return func(yield func(K, V) bool) {
        for k, v := range seq {
            if pred(k, v) {
                if !yield(k, v) {
                    return
                }
            }
        }
    }
}

func Reduce[V any, U any](seq iter.Seq[V], init U, fn func(U, V) U) U {
    acc := init
    for v := range seq {
        acc = fn(acc, v)
    }
    return acc
}

Использование композиции конвейеров

type Order struct {
    ID     int
    Status string
    Amount float64
}

func OrdersFromDB(db *sql.DB) iter.Seq[Order] {
    return func(yield func(Order) bool) {
        rows, _ := db.Query("SELECT id, status, amount FROM orders")
        if rows != nil {
            defer rows.Close()
            for rows.Next() {
                var o Order
                rows.Scan(&o.ID, &o.Status, &o.Amount)
                if !yield(o) {
                    return
                }
            }
        }
    }
}

func main() {
    db, _ := sql.Open("postgres", "dsn")

    total := Reduce(
        Map(
            Filter(
                OrdersFromDB(db),
                func(o Order) bool { return o.Status == "active" },
            ),
            func(o Order) float64 { return o.Amount * 1.1 },
        ),
        0.0,
        func(acc float64, v float64) float64 { return acc + v },
    )

    fmt.Printf("Total: %.2f\n", total)
}

Нулевое промежуточное выделение памяти: Filter, Map и Reduce объединены в цепочку — каждый элемент обрабатывается ровно один раз.

Поток выполнения конвейера

OrdersFromDB → Filter(active) → Map(×1.1) → Reduce(+) → total
     │              │                │            │
     │  Order{1,    │  Status==      │  Amount*1.1 │  acc+v
     │  "active",   │  "active"?     │             │
     │  100.0}      │                │             │
     │      ──────→ │  ✓ pass        │             │
     │              │      ──────→   │  110.0      │
     │              │                │   ──────→   │  110.0
     │
     │  Order{2,    │  Status!=      │             │
     │  "closed",   │  "active"      │             │
     │  200.0}      │  ✗ filtered    │             │
     │      ──────→ │  skip          │             │

Дополнительные операторы конвейера

func FlatMap[V any, U any](seq iter.Seq[V], fn func(V) iter.Seq[U]) iter.Seq[U] {
    return func(yield func(U) bool) {
        for v := range seq {
            for u := range fn(v) {
                if !yield(u) {
                    return
                }
            }
        }
    }
}

func Zip[V any, U any](seq1 iter.Seq[V], seq2 iter.Seq[U]) iter.Seq2[V, U] {
    return func(yield func(V, U) bool) {
        next1, stop1 := iter.Pull(seq1)
        defer stop1()
        next2, stop2 := iter.Pull(seq2)
        defer stop2()

        for {
            v1, ok1 := next1()
            v2, ok2 := next2()
            if !ok1 || !ok2 {
                return
            }
            if !yield(v1, v2) {
                return
            }
        }
    }
}

func Enumerate[V any](seq iter.Seq[V]) iter.Seq2[int, V] {
    return func(yield func(int, V) bool) {
        i := 0
        for v := range seq {
            if !yield(i, v) {
                return
            }
            i++
        }
    }
}

func Chunk[V any](seq iter.Seq[V], size int) iter.Seq[[]V] {
    return func(yield func([]V) bool) {
        chunk := make([]V, 0, size)
        for v := range seq {
            chunk = append(chunk, v)
            if len(chunk) == size {
                if !yield(chunk) {
                    return
                }
                chunk = make([]V, 0, size)
            }
        }
        if len(chunk) > 0 {
            yield(chunk)
        }
    }
}

Паттерн 4: Ленивые вычисления и бесконечные последовательности

Проблема: Растрата на предварительные вычисления всех результатов

func Fibonacci(n int) []int {
    result := make([]int, n)
    if n > 0 {
        result[0] = 0
    }
    if n > 1 {
        result[1] = 1
    }
    for i := 2; i < n; i++ {
        result[i] = result[i-1] + result[i-2]
    }
    return result
}

Нужны первые 10 чисел Фибоначчи? Необходимо указать n. Не знаете сколько? Вычислите «достаточно большое» значение заранее.

Решение: Бесконечный итератор + ленивые вычисления

package lazy

import "iter"

func Fibonacci() iter.Seq[int] {
    return func(yield func(int) bool) {
        a, b := 0, 1
        for {
            if !yield(a) {
                return
            }
            a, b = b, a+b
        }
    }
}

func NaturalNumbers() iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := 0; ; i++ {
            if !yield(i) {
                return
            }
        }
    }
}

func Repeat[V any](v V) iter.Seq[V] {
    return func(yield func(V) bool) {
        for {
            if !yield(v) {
                return
            }
        }
    }
}

func Iterate[V any](init V, fn func(V) V) iter.Seq[V] {
    return func(yield func(V) bool) {
        v := init
        for {
            if !yield(v) {
                return
            }
            v = fn(v)
        }
    }
}

func Cycle[V any](seq iter.Seq[V]) iter.Seq[V] {
    return func(yield func(V) bool) {
        for {
            for v := range seq {
                if !yield(v) {
                    return
                }
            }
        }
    }
}

Использование ленивых последовательностей

func main() {
    for v := range Take(Fibonacci(), 10) {
        fmt.Print(v, " ")
    }
    fmt.Println()

    squares := Map(
        Take(NaturalNumbers(), 5),
        func(n int) int { return n * n },
    )
    for v := range squares {
        fmt.Print(v, " ")
    }
    fmt.Println()

    powersOf2 := Iterate(1, func(v int) int { return v * 2 })
    for v := range Take(powersOf2, 8) {
        fmt.Print(v, " ")
    }
    fmt.Println()
}

Ленивая обработка файлов

func FileLines(path string) iter.Seq[string] {
    return func(yield func(string) bool) {
        f, err := os.Open(path)
        if err != nil {
            return
        }
        defer f.Close()

        scanner := bufio.NewScanner(f)
        for scanner.Scan() {
            if !yield(scanner.Text()) {
                return
            }
        }
    }
}

func Grep(pattern string, lines iter.Seq[string]) iter.Seq[string] {
    re := regexp.MustCompile(pattern)
    return Filter(lines, func(line string) bool {
        return re.MatchString(line)
    })
}

func main() {
    errors := Grep("ERROR", FileLines("/var/log/app.log"))
    for line := range Take(errors, 100) {
        fmt.Println(line)
    }
}

Лог-файл размером 10 ГБ? Читаются только первые 100 строк с ERROR — использование памяти почти нулевое.


Паттерн 5: Параллельные итераторы и Fan-out

Проблема: Узкое место производительности однопоточного итератора

func ProcessImages(images iter.Seq[Image]) []Result {
    var results []Result
    for img := range images {
        r := expensiveTransform(img)
        results = append(results, r)
    }
    return results
}

1000 изображений, по 100 мс каждое, всего 100 секунд. Утилизация CPU всего 12,5% (используется 1 из 8 ядер).

Решение: Параллельный итератор Fan-out

package concurrent

import (
    "iter"
    "sync"
)

func FanOut[V any, U any](seq iter.Seq[V], workers int, fn func(V) U) iter.Seq[U] {
    return func(yield func(U) bool) {
        inputCh := make(chan V)
        outputCh := make(chan U)

        var wg sync.WaitGroup
        for i := 0; i < workers; i++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                for v := range inputCh {
                    outputCh <- fn(v)
                }
            }()
        }

        go func() {
            for v := range seq {
                inputCh <- v
            }
            close(inputCh)
            wg.Wait()
            close(outputCh)
        }()

        for u := range outputCh {
            if !yield(u) {
                return
            }
        }
    }
}

func FanOutOrdered[V any, U any](seq iter.Seq[V], workers int, fn func(V) U) iter.Seq[U] {
    return func(yield func(U) bool) {
        type indexedResult struct {
            index int
            value U
        }

        next, stop := iter.Pull(seq)
        defer stop()

        type indexedInput[V any] struct {
            index int
            value V
        }

        inputCh := make(chan indexedInput[V], workers)
        outputCh := make(chan indexedResult, workers)

        var wg sync.WaitGroup
        for i := 0; i < workers; i++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                for inp := range inputCh {
                    outputCh <- indexedResult{
                        index: inp.index,
                        value: fn(inp.value),
                    }
                }
            }()
        }

        go func() {
            idx := 0
            for {
                v, ok := next()
                if !ok {
                    break
                }
                inputCh <- indexedInput[V]{index: idx, value: v}
                idx++
            }
            close(inputCh)
            wg.Wait()
            close(outputCh)
        }()

        results := make(map[int]U)
        nextIdx := 0
        for res := range outputCh {
            results[res.index] = res.value
            for {
                r, ok := results[nextIdx]
                if !ok {
                    break
                }
                delete(results, nextIdx)
                if !yield(r) {
                    return
                }
                nextIdx++
            }
        }
    }
}

Использование параллельных итераторов

type Image struct {
    Path string
    Data []byte
}

type Result struct {
    Path      string
    Thumbnail []byte
}

func LoadImages(paths iter.Seq[string]) iter.Seq[Image] {
    return Map(paths, func(p string) Image {
        data, _ := os.ReadFile(p)
        return Image{Path: p, Data: data}
    })
}

func expensiveTransform(img Image) Result {
    thumbnail := resizeImage(img.Data, 100, 100)
    return Result{Path: img.Path, Thumbnail: thumbnail}
}

func main() {
    paths := SliceIterator([]string{"a.jpg", "b.jpg", "c.jpg"})

    results := FanOut(
        LoadImages(paths),
        runtime.NumCPU(),
        expensiveTransform,
    )

    for r := range results {
        fmt.Printf("Done: %s\n", r.Path)
    }
}

Архитектура параллельного итератора

               ┌──────────┐
               │ Seq[V]   │
               │ (source) │
               └────┬─────┘
                    │
              ┌─────▼─────┐
              │ inputCh   │
              └─────┬─────┘
                    │
        ┌───────────┼───────────┐
        │           │           │
   ┌────▼───┐  ┌───▼────┐  ┌──▼─────┐
   │Worker 1│  │Worker 2│  │Worker N│
   │ fn(v)  │  │ fn(v)  │  │ fn(v)  │
   └────┬───┘  └───┬────┘  └──┬─────┘
        │          │          │
        └───────────┼──────────┘
                    │
              ┌─────▼─────┐
              │ outputCh  │
              └─────┬─────┘
                    │
              ┌─────▼─────┐
              │ yield(U)  │
              │ (consumer)│
              └───────────┘

Паттерн 6: Обработка ошибок в итераторах

Проблема: Ошибки, поглощённые в итераторах

func ReadRecords(path string) iter.Seq[Record] {
    return func(yield func(Record) bool) {
        file, _ := os.Open(path)
        defer file.Close()

        decoder := json.NewDecoder(file)
        for decoder.More() {
            var r Record
            if err := decoder.Decode(&r); err != nil {
                return
            }
            if !yield(r) {
                return
            }
        }
    }
}

Когда decoder.Decode завершается ошибкой, она полностью теряется. Вызывающий код не может определить, завершилась ли итерация нормально или из-за ошибки.

Решение: Итераторы с распространением ошибок

package itererr

import "iter"

type Result[V any] struct {
    Value V
    Err   error
}

func SeqWithError[V any](seq iter.Seq[Result[V]]) (iter.Seq[V], *error) {
    var firstErr error
    values := func(yield func(V) bool) {
        for r := range seq {
            if r.Err != nil {
                if firstErr == nil {
                    firstErr = r.Err
                }
                return
            }
            if !yield(r.Value) {
                return
            }
        }
    }
    return values, &firstErr
}

func Wrap[V any](seq iter.Seq[V], errPtr *error) iter.Seq[Result[V]] {
    return func(yield func(Result[V]) bool) {
        for v := range seq {
            if *errPtr != nil {
                return
            }
            if !yield(Result[V]{Value: v}) {
                return
            }
        }
    }
}

Практическое применение: Итератор строк базы данных

package dbiter

import (
    "database/sql"
    "iter"
)

type RowResult[T any] struct {
    Value T
    Err   error
}

func QueryRows[T any](db *sql.DB, query string, scan func(*sql.Rows) (T, error)) iter.Seq[RowResult[T]] {
    return func(yield func(RowResult[T]) bool) {
        rows, err := db.Query(query)
        if err != nil {
            yield(RowResult[T]{Err: err})
            return
        }
        defer rows.Close()

        for rows.Next() {
            v, err := scan(rows)
            if err != nil {
                yield(RowResult[T]{Err: err})
                return
            }
            if !yield(RowResult[T]{Value: v}) {
                return
            }
        }
        if err := rows.Err(); err != nil {
            yield(RowResult[T]{Err: err})
        }
    }
}
type Product struct {
    ID    int
    Name  string
    Price float64
}

func main() {
    db, _ := sql.Open("postgres", "dsn")

    products := QueryRows(db,
        "SELECT id, name, price FROM products",
        func(rows *sql.Rows) (Product, error) {
            var p Product
            err := rows.Scan(&p.ID, &p.Name, &p.Price)
            return p, err
        },
    )

    for r := range products {
        if r.Err != nil {
            log.Printf("Iteration error: %v", r.Err)
            break
        }
        fmt.Printf("%s: $%.2f\n", r.Value.Name, r.Value.Price)
    }
}

Конвейер распространения ошибок

func SafeMap[V any, U any](seq iter.Seq[RowResult[V]], fn func(V) (U, error)) iter.Seq[RowResult[U]] {
    return func(yield func(RowResult[U]) bool) {
        for r := range seq {
            if r.Err != nil {
                if !yield(RowResult[U]{Err: r.Err}) {
                    return
                }
                return
            }
            u, err := fn(r.Value)
            if err != nil {
                if !yield(RowResult[U]{Err: err}) {
                    return
                }
                return
            }
            if !yield(RowResult[U]{Value: u}) {
                return
            }
        }
    }
}

func SafeFilter[V any](seq iter.Seq[RowResult[V]], pred func(V) (bool, error)) iter.Seq[RowResult[V]] {
    return func(yield func(RowResult[V]) bool) {
        for r := range seq {
            if r.Err != nil {
                if !yield(r) {
                    return
                }
                return
            }
            ok, err := pred(r.Value)
            if err != nil {
                if !yield(RowResult[V]{Err: err}) {
                    return
                }
                return
            }
            if ok {
                if !yield(r) {
                    return
                }
            }
        }
    }
}

Паттерн 7: Проектирование продакшен-библиотеки итераторов

Принципы проектирования

┌─────────────────────────────────────────────┐
│      Production Iterator Library Principles  │
├─────────────────────────────────────────────┤
│  1. Zero allocation: no intermediate slices  │
│  2. Composable: all ops return iter.Seq      │
│  3. Terminable: release on yield=false       │
│  4. Observable: error propagation & metrics  │
│  5. Testable: pure functions, no side effects│
└─────────────────────────────────────────────┘

Полный набор инструментов итераторов

package itool

import "iter"

type Seq[V any] = iter.Seq[V]

type Seq2[K any, V any] = iter.Seq2[K, V]

func FromSlice[V any](s []V) Seq[V] {
    return func(yield func(V) bool) {
        for _, v := range s {
            if !yield(v) {
                return
            }
        }
    }
}

func FromMap[K comparable, V any](m map[K]V) Seq2[K, V] {
    return func(yield func(K, V) bool) {
        for k, v := range m {
            if !yield(k, v) {
                return
            }
        }
    }
}

func FromChannel[V any](ch <-chan V) Seq[V] {
    return func(yield func(V) bool) {
        for v := range ch {
            if !yield(v) {
                return
            }
        }
    }
}

func Generate[V any](fn func() (V, bool)) Seq[V] {
    return func(yield func(V) bool) {
        for {
            v, ok := fn()
            if !ok || !yield(v) {
                return
            }
        }
    }
}

func Concat[V any](seqs ...Seq[V]) Seq[V] {
    return func(yield func(V) bool) {
        for _, seq := range seqs {
            for v := range seq {
                if !yield(v) {
                    return
                }
            }
        }
    }
}

func Distinct[V comparable](seq Seq[V]) Seq[V] {
    return func(yield func(V) bool) {
        seen := make(map[V]bool)
        for v := range seq {
            if !seen[v] {
                seen[v] = true
                if !yield(v) {
                    return
                }
            }
        }
    }
}

func Reverse[V any](seq Seq[V]) Seq[V] {
    return func(yield func(V) bool) {
        var items []V
        for v := range seq {
            items = append(items, v)
        }
        for i := len(items) - 1; i >= 0; i-- {
            if !yield(items[i]) {
                return
            }
        }
    }
}

func Skip[V any](seq Seq[V], n int) Seq[V] {
    return func(yield func(V) bool) {
        i := 0
        for v := range seq {
            if i >= n {
                if !yield(v) {
                    return
                }
            }
            i++
        }
    }
}

func TakeWhile[V any](seq Seq[V], pred func(V) bool) Seq[V] {
    return func(yield func(V) bool) {
        for v := range seq {
            if !pred(v) {
                return
            }
            if !yield(v) {
                return
            }
        }
    }
}

func SkipWhile[V any](seq Seq[V], pred func(V) bool) Seq[V] {
    return func(yield func(V) bool) {
        skipping := true
        for v := range seq {
            if skipping {
                if pred(v) {
                    continue
                }
                skipping = false
            }
            if !yield(v) {
                return
            }
        }
    }
}

func Count[V any](seq Seq[V]) int {
    n := 0
    for range seq {
        n++
    }
    return n
}

func Any[V any](seq Seq[V], pred func(V) bool) bool {
    for v := range seq {
        if pred(v) {
            return true
        }
    }
    return false
}

func All[V any](seq Seq[V], pred func(V) bool) bool {
    for v := range seq {
        if !pred(v) {
            return false
        }
    }
    return true
}

func ForEach[V any](seq Seq[V], fn func(V)) {
    for v := range seq {
        fn(v)
    }
}

func ToSlice[V any](seq Seq[V]) []V {
    var result []V
    for v := range seq {
        result = append(result, v)
    }
    return result
}

func ToMap[K comparable, V any](seq Seq2[K, V]) map[K]V {
    result := make(map[K]V)
    for k, v := range seq {
        result[k] = v
    }
    return result
}

func GroupBy[K comparable, V any](seq Seq2[K, V]) map[K][]V {
    result := make(map[K][]V)
    for k, v := range seq {
        result[k] = append(result[k], v)
    }
    return result
}

Примеры использования

func main() {
    nums := FromSlice([]int{1, 2, 3, 4, 5, 4, 3, 2, 1})

    result := ToSlice(
        Distinct(
            Filter(
                Map(nums, func(n int) int { return n * 2 }),
                func(n int) bool { return n > 4 },
            ),
        ),
    )

    fmt.Println(result)

    evenCount := Count(Filter(FromSlice([]int{1, 2, 3, 4, 5, 6}), func(n int) bool {
        return n%2 == 0
    }))
    fmt.Println("Even count:", evenCount)

    hasNegative := Any(FromSlice([]int{1, 2, 3}), func(n int) bool {
        return n < 0
    })
    fmt.Println("Has negative:", hasNegative)
}

5 распространённых ошибок и решения

Ошибка 1: Захват переменных цикла в итераторах

func BuggyFactory() []iter.Seq[int] {
    var seqs []iter.Seq[int]
    for i := 0; i < 3; i++ {
        seqs = append(seqs, func(yield func(int) bool) {
            yield(i)
        })
    }
    return seqs
}

Все итераторы возвращают 3. i захватывается замыканием, значение равно 3 после завершения цикла.

Исправление:

func FixedFactory() []iter.Seq[int] {
    var seqs []iter.Seq[int]
    for i := 0; i < 3; i++ {
        i := i
        seqs = append(seqs, func(yield func(int) bool) {
            yield(i)
        })
    }
    return seqs
}

Ошибка 2: Забытый вызов stop вызывает утечку ресурсов

next, stop := iter.Pull(FileLines("big.log"))
v, ok := next()
fmt.Println(v)

Файл никогда не закрывается.

Исправление:

next, stop := iter.Pull(FileLines("big.log"))
defer stop()
v, ok := next()
fmt.Println(v)

Ошибка 3: Итераторы не реентерабельны

seq := Fibonacci()
for v := range Take(seq, 5) {
    fmt.Println(v)
}
for v := range Take(seq, 5) {
    fmt.Println(v)
}

Второй range ничего не выводит. Итераторы одноразовые.

Исправление:

fibFactory := func() iter.Seq[int] { return Fibonacci() }

for v := range Take(fibFactory(), 5) {
    fmt.Println(v)
}
for v := range Take(fibFactory(), 5) {
    fmt.Println(v)
}

Ошибка 4: Паника в итераторах

func RiskySeq() iter.Seq[int] {
    return func(yield func(int) bool) {
        panic("oops")
    }
}

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recovered:", r)
        }
    }()
    for v := range RiskySeq() {
        fmt.Println(v)
    }
}

В Go 1.24 паники из range over func могут быть восстановлены извне. Но не полагайтесь на это поведение — итераторы должны обрабатывать свои собственные ошибки.

Ошибка 5: Параллельный range по одному итератору

seq := FromSlice([]int{1, 2, 3, 4, 5})

var wg sync.WaitGroup
for i := 0; i < 3; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        for v := range seq {
            fmt.Println(v)
        }
    }()
}
wg.Wait()

iter.Seq не является потокобезопасным. Одновременный range из нескольких горутин вызывает гонку данных.

Исправление: Используйте паттерн Fan-out или создавайте независимые итераторы для каждой горутины.


10 распространённых ошибок и способы их устранения

Ошибка Причина Решение
cannot range over seq (variable of type func(yield func(int) bool)) Сигнатура функции не соответствует iter.Seq Убедитесь, что сигнатура func(yield func(V) bool)
cannot use function as type iter.Seq[int] Несоответствие типа параметра функции yield Проверьте, что тип параметра yield соответствует параметру типа Seq
iter.Pull: iterator did not call stop Функция stop Pull-итератора не вызвана Всегда используйте defer stop()
panic: range over func: yield called after return yield вызван после возврата итератора Проверьте, не вызывают ли горутины yield после возврата
deadlock inputCh/outputCh Fan-out не закрыты Убедитесь, что все горутины завершились перед закрытием каналов
data race Несколько горутин выполняют range по одному Seq Каждая горутина использует независимый итератор
out of memory Бесконечный итератор без Take Всегда используйте Take/Skip с бесконечными последовательностями
goroutine leak Горутины в итераторе никогда не завершаются Используйте context или done-канал для управления выходом
unexpected EOF during iteration Файл изменён во время итерации Используйте файловые блокировки или снимки
yield returns false but iteration continues Возвращаемое значение yield не проверяется Проверяйте возвращаемое значение yield после каждого вызова, возвращайте при false

Продвинутые советы по оптимизации

Совет 1: Предварительное выделение для снижения нагрузки на GC

func ToSlicePrealloc[V any](seq Seq[V], hint int) []V {
    result := make([]V, 0, hint)
    for v := range seq {
        result = append(result, v)
    }
    return result[:len(result)]
}

Если вы знаете приблизительное количество, предварительно выделите память, чтобы избежать множественных расширений.

Совет 2: Пакетный итератор для сокращения системных вызовов

func Batched[V any](seq Seq[V], batchSize int) Seq[[]V] {
    return func(yield func([]V) bool) {
        batch := make([]V, 0, batchSize)
        for v := range seq {
            batch = append(batch, v)
            if len(batch) == batchSize {
                if !yield(batch) {
                    return
                }
                batch = make([]V, 0, batchSize)
            }
        }
        if len(batch) > 0 {
            yield(batch)
        }
    }
}

Для пакетных вставок в базу данных фиксируйте каждые 100 строк, чтобы сократить количество сетевых round trip.

Совет 3: Итератор + Context для контроля тайм-аута

func WithContext[V any](ctx context.Context, seq Seq[V]) Seq[V] {
    return func(yield func(V) bool) {
        for v := range seq {
            select {
            case <-ctx.Done():
                return
            default:
                if !yield(v) {
                    return
                }
            }
        }
    }
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

for v := range WithContext(ctx, SlowIterator()) {
    fmt.Println(v)
}

Сравнительный анализ: Итераторы vs Каналы vs Срезы

Аспект Итератор (iter.Seq) Канал (chan) Срез ([]T)
Использование памяти O(1) O(n) буфер O(n)
Ленивые вычисления Да Нет Нет
Бесконечные последовательности Да Нет Нет
Потокобезопасность Нет Да Нет
Компонуемость Отличная (цепочки функций) Средняя (нужна горутина) Плохая (промежуточные срезы)
Обработка ошибок Требует обёртки Нативная поддержка Прямой возврат ошибки
Реентерабельность Нет Нет Да
Производительность Нулевое выделение Накладные расходы на блокировки Накладные расходы на копирование
Случай использования Конвейеры данных, ленивые вычисления Параллельное взаимодействие Малые наборы данных, произвольный доступ
Версия Go 1.24+ 1.0+ 1.0+
Сложность отладки Средняя Высокая Низкая

Дерево решений

Need lazy evaluation or infinite sequences?
├── Yes → Iterator
└── No
    ├── Need concurrent communication?
    │   └── Yes → Channel
    └── No
        ├── Small data + random access?
        │   └── Yes → Slice
        └── No → Iterator

Рекомендуемые онлайн-инструменты

Внешние ссылки


Итоги

Паттерны итераторов Go 1.24 предоставляют Go нативные возможности конвейеров данных с нулевым выделением памяти и компонуемостью. 7 основных паттернов охватывают весь спектр от базового обхода до проектирования продакшен-библиотек:

  1. Базовый итератор range over func — Отправная точка, yield управляет потоком
  2. Преобразование Push/Pull итераторов — iter.Pull обеспечивает ручное управление
  3. Композиция конвейеров данных — Цепочки Map/Filter/Reduce с нулевым промежуточным выделением памяти
  4. Ленивые вычисления и бесконечные последовательности — Вычисления по требованию, обработка бесконечных потоков
  5. Параллельные итераторы и Fan-out — Параллельное потребление несколькими горутинами
  6. Обработка ошибок в итераторах — Паттерн RowResult для изящного распространения ошибок
  7. Проектирование продакшен-библиотеки итераторов — Нулевое выделение, компонуемость, завершаемость

Итераторы — это не замена каналам или срезам. Это новый член набора инструментов обработки данных Go — когда вам нужны ленивые вычисления и компоновка конвейеров с нулевым выделением памяти, итераторы — лучший выбор.

Рекомендуемое чтение:

Попробуйте эти локальные браузерные инструменты — регистрация не требуется →

#Go#迭代器#Iterator#range over func#数据管道#Go 1.24#2026#编程语言