Padrões de Iterador do Go 1.24: 7 Padrões de Produção do Range Over Func a Pipelines de Dados

编程语言(Atualizado em 14 de jul. de 2026)

Quando Loops for Encontram Funções: A Mudança de Paradigma de Iteradores do Go

Na semana passada, refatorei um serviço de processamento de dados com 3 níveis de loops for aninhados processando 100K registros — a memória disparou para 2GB porque cada nível tinha que armazenar resultados intermediários como slices antes de passar para o próximo. Após mudar para um pipeline de iteradores, a memória caiu para 15MB e o processamento foi 30% mais rápido. A mudança principal: chega de "coletar e depois processar" — em vez disso, "iterar e processar em tempo real".

O Go 1.24 estabilizou oficialmente a sintaxe range over func e o pacote iter, dando ao Go iteradores nativos, de alocação zero e compostos. Isso não é açúcar sintático — é uma mudança fundamental nos paradigmas de processamento de dados. Este artigo aborda 7 padrões de iteradores Go de nível de produção para ajudá-lo a construir pipelines de dados eficientes, elegantes e compostos.


Pontos Principais

  • range over func é a sintaxe principal de iterador: O Go 1.24 permite que funções sejam iteradas diretamente com range
  • Conversão de iteradores Push/Pull: Entenda ambas as direções, domine a conversão com iter.Pull
  • Composição de pipeline de dados: Cadeia de composição Map/Filter/Reduce com alocação intermediária zero
  • Avaliação preguiçosa e sequências infinitas: Compute sob demanda, processe fluxos de dados infinitos
  • Iteradores concorrentes e Fan-out: Múltiplas goroutines consumindo iteradores em paralelo
  • Tratamento de erros em iteradores: Trate erros graciosamente durante a iteração
  • Design de biblioteca de iteradores de nível de produção: Construa kits de ferramentas de iteradores reutilizáveis

Sumário

  1. Referência de Conceitos Principais de Iteradores Go
  2. Padrão 1: Iterador Básico range over func
  3. Padrão 2: Conversão de Iteradores Push/Pull
  4. Padrão 3: Composição de Pipeline de Dados (Map/Filter/Reduce)
  5. Padrão 4: Avaliação Preguiçosa e Sequências Infinitas
  6. Padrão 5: Iteradores Concorrentes e Fan-out
  7. Padrão 6: Tratamento de Erros em Iteradores
  8. Padrão 7: Design de Biblioteca de Iteradores de Nível de Produção
  9. 5 Armadilhas Comuns e Soluções
  10. 10 Erros Comuns e Solução de Problemas
  11. Dicas Avançadas de Otimização
  12. Análise Comparativa: Iteradores vs Canais vs Slices
  13. Ferramentas Online Recomendadas
  14. Resumo

Referência de Conceitos Principais de Iteradores Go

Conceito Assinatura Propósito Exemplo
Função iteradora func(yield func(V) bool) Iterador de valor único func(yield func(int) bool)
Iterador chave-valor func(yield func(K, V) bool) Iteração de par chave-valor func(yield func(int, string) bool)
Iterador Pull func() (V, bool) Pull sob demanda next, stop := iter.Pull(seq)
iter.Pull func(Seq[V]) (func() (V, bool), func()) Conversão de Push para Pull Travessia orientada pelo consumidor
iter.Stop Função de parada embutida Terminação antecipada stop() libera recursos
Valor de retorno do yield bool Controla continuação/parada da iteração yield(v) retorna false para parar
Avaliação preguiçosa Computação adiada Gera valores sob demanda Sequências infinitas, linhas de arquivo
Composição de pipeline Encadeamento de funções Alocação intermediária zero Filter(Map(Seq, fn), pred)

Padrão 1: Iterador Básico range over func

Problema: Armadilhas de Memória da Travessia Tradicional

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 milhão de usuários? 1 milhão de structs User carregados na memória. Você só precisa dos primeiros 10? Azar — carregue todos primeiro.

Solução: Iterador 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++
        }
    }
}

Uso:

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

Quando o break é executado, yield retorna false, e a função iteradora retorna imediatamente — apenas 10 linhas consultadas, conexão com o banco de dados devidamente fechada.

Fluxo de Execução do Iterador

┌─────────────┐     yield(v)      ┌──────────────┐
│  Iterador    │ ──────────────→  │   loop range  │
│  (produtor)  │                   │  (consumidor) │
│              │  ←──────────────  │              │
│              │   yield retorna   │              │
│              │      bool         │              │
└─────────────┘                   └──────────────┘
      │                                  │
      │  yield retorna false → return    │
      │  (terminação antecipada)         │
      └──────────────────────────────────┘

Iterador de Valor Único vs Chave-Valor

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)
}

Padrão 2: Conversão de Iteradores Push/Pull

Diferenças entre Iteradores Push e Pull

Iterador Push (iter.Seq)            Iterador Pull (func() (V, bool))
┌──────────────────┐               ┌──────────────────┐
│  Produtor empurra │               │  Consumidor puxa  │
│  yield(v) → cons. │               │  next() → prod.   │
│                    │               │                    │
│  Bom para: range   │               │  Bom para: manual  │
│  Bom para: pipes   │               │  Bom para: peek    │
│  Bom para: lazy    │               │  Bom para: interop │
└──────────────────┘               └──────────────────┘
         │                                  │
         │      conversão iter.Pull()       │
         └──────────────────────────────────┘

Usando a Conversão 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("Terminação antecipada")
            break
        }
    }
}

Aplicações Práticas de Iteradores Pull: Peek e 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))

Importante: Limpeza de Recursos do 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("Primeira linha:", v)
    }
}

defer stop() garante que o arquivo seja devidamente fechado mesmo que apenas um valor seja consumido.


Padrão 3: Composição de Pipeline de Dados (Map/Filter/Reduce)

Problema: Loops Aninhados e Slices Intermediários

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 travessias, 2 slices intermediários. Com grandes conjuntos de dados, a pressão de memória e GC é significativa.

Solução: Pipeline de Iteradores

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
}

Usando Composição de Pipeline

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)
}

Alocação intermediária zero: Filter, Map e Reduce são encadeados — cada elemento é processado exatamente uma vez.

Fluxo de Execução do Pipeline

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

Mais Operadores de Pipeline

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)
        }
    }
}

Padrão 4: Avaliação Preguiçosa e Sequências Infinitas

Problema: Desperdício ao Pré-computar Todos os Resultados

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
}

Precisa dos primeiros 10 números de Fibonacci? Deve especificar n. Não sabe quantos? Calcule um valor "grande o suficiente" antecipadamente.

Solução: Iterador Infinito + Avaliação Preguiçosa

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
                }
            }
        }
    }
}

Usando Sequências Preguiçosas

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()
}

Processamento Preguiçoso de Arquivos

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)
    }
}

Arquivo de log de 10GB? Apenas leia as primeiras 100 linhas com ERROR — uso de memória próximo de zero.


Padrão 5: Iteradores Concorrentes e Fan-out

Problema: Gargalo de Desempenho de Iterador Single-threaded

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

1000 imagens, 100ms cada, total de 100 segundos. Utilização de CPU apenas 12,5% (1 de 8 núcleos usados).

Solução: Iterador Concorrente 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++
            }
        }
    }
}

Usando Iteradores Concorrentes

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("Concluído: %s\n", r.Path)
    }
}

Arquitetura de Iterador Concorrente

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

Padrão 6: Tratamento de Erros em Iteradores

Problema: Erros Engolidos em Iteradores

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
            }
        }
    }
}

Quando decoder.Decode falha, o erro é completamente perdido. O chamador não consegue dizer se a iteração terminou normalmente ou devido a um erro.

Solução: Iteradores com Propagação de Erros

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
            }
        }
    }
}

Aplicação Prática: Iterador de Linhas de Banco de Dados

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("Erro de iteração: %v", r.Err)
            break
        }
        fmt.Printf("%s: R$%.2f\n", r.Value.Name, r.Value.Price)
    }
}

Pipeline de Propagação de Erros

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
                }
            }
        }
    }
}

Padrão 7: Design de Biblioteca de Iteradores de Nível de Produção

Princípios de Design

┌─────────────────────────────────────────────┐
│  Princípios de Biblioteca de Iteradores     │
│  de Produção                                │
├─────────────────────────────────────────────┤
│  1. Alocação zero: sem slices intermediários│
│  2. Composto: todas ops retornam iter.Seq   │
│  3. Terminável: liberar em yield=false      │
│  4. Observável: propagação de erros & métr. │
│  5. Testável: funções puras, sem efeitos    │
│     colaterais                              │
└─────────────────────────────────────────────┘

Kit de Ferramentas Completo de Iteradores

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
}

Exemplos de Uso

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("Contagem de pares:", evenCount)

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

5 Armadilhas Comuns e Soluções

Armadilha 1: Capturando Variáveis de Loop em Iteradores

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
}

Todos os iteradores retornam 3. i é capturado por closure, o valor é 3 quando o loop termina.

Correção:

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
}

Armadilha 2: Esquecer de Chamar stop Causa Vazamentos de Recursos

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

O arquivo nunca é fechado.

Correção:

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

Armadilha 3: Iteradores Não São Reentrantes

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

O segundo range não produz nada. Iteradores são de uso único.

Correção:

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)
}

Armadilha 4: Panic em Iteradores

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

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

No Go 1.24, panics de range over func podem ser recuperados externamente. Mas não dependa desse comportamento — iteradores devem tratar seus próprios erros.

Armadilha 5: Range Concorrente no Mesmo Iterador

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 não é seguro para concorrência. Múltiplas goroutines iterando simultaneamente causa data races.

Correção: Use o padrão Fan-out, ou crie iteradores independentes para cada goroutine.


10 Erros Comuns e Solução de Problemas

Erro Causa Solução
cannot range over seq (variable of type func(yield func(int) bool)) Assinatura da função não corresponde a iter.Seq Garanta que a assinatura seja func(yield func(V) bool)
cannot use function as type iter.Seq[int] Incompatibilidade do tipo do parâmetro yield Verifique se o tipo do parâmetro yield corresponde ao parâmetro de tipo Seq
iter.Pull: iterator did not call stop Stop do iterador Pull não foi chamado Sempre use defer stop()
panic: range over func: yield called after return yield chamado após retorno do iterador Verifique se goroutines chamam yield após retorno
deadlock inputCh/outputCh do Fan-out não fechados Garanta que todas as goroutines saiam antes de fechar os canais
data race Múltiplas goroutines iterando o mesmo Seq Cada goroutine usa iterador independente
out of memory Iterador infinito sem Take Sempre use Take/Skip com sequências infinitas
goroutine leak Goroutines no iterador nunca saem Use context ou canal done para controle de saída
unexpected EOF during iteration Arquivo modificado durante iteração de arquivo Use locks de arquivo ou snapshots
yield returns false but iteration continues Valor de retorno do yield não verificado Verifique retorno do yield após cada chamada, retorne em false

Dicas Avançadas de Otimização

Dica 1: Pré-alocação para Reduzir Pressão do 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)]
}

Quando você sabe a contagem aproximada, pré-aloque para evitar múltiplos redimensionamentos.

Dica 2: Iterador em Lote para Reduzir Chamadas de Sistema

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)
        }
    }
}

Para inserções em lote no banco de dados, faça commit a cada 100 linhas para reduzir viagens de rede.

Dica 3: Iterador + Context para Controle de Timeout

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)
}

Análise Comparativa: Iteradores vs Canais vs Slices

Dimensão Iterador (iter.Seq) Canal (chan) Slice ([]T)
Uso de memória O(1) O(n) buffer O(n)
Avaliação preguiçosa Sim Não Não
Sequências infinitas Sim Não Não
Seguro para concorrência Não Sim Não
Componibilidade Excelente (cadeia de funções) Médio (precisa de goroutine) Ruim (slices intermediários)
Tratamento de erros Precisa de wrapping Suporte nativo Retorno direto de erro
Reentrante Não Não Sim
Desempenho Alocação zero Sobrecarga de lock Sobrecarga de cópia
Caso de uso Pipelines de dados, avaliação preguiçosa Comunicação concorrente Pequenos conjuntos de dados, acesso aleatório
Versão do Go 1.24+ 1.0+ 1.0+
Dificuldade de depuração Média Alta Baixa

Árvore de Decisão

Precisa de avaliação preguiçosa ou sequências infinitas?
├── Sim → Iterador
└── Não
    ├── Precisa de comunicação concorrente?
    │   └── Sim → Canal
    └── Não
        ├── Dados pequenos + acesso aleatório?
        │   └── Sim → Slice
        └── Não → Iterador

Ferramentas Online Recomendadas

Referências Externas


Resumo

Os padrões de iterador do Go 1.24 dão ao Go capacidades nativas de pipeline de dados com alocação zero e composição. Os 7 padrões principais cobrem todo o espectro, da travessia básica ao design de biblioteca de nível de produção:

  1. Iterador básico range over func — O ponto de partida, yield controla o fluxo
  2. Conversão de iteradores Push/Pull — iter.Pull permite controle manual
  3. Composição de pipeline de dados — Encadeamento Map/Filter/Reduce com alocação intermediária zero
  4. Avaliação preguiçosa e sequências infinitas — Compute sob demanda, processe fluxos infinitos
  5. Iteradores concorrentes e Fan-out — Consumo paralelo com múltiplas goroutines
  6. Tratamento de erros em iteradores — Padrão RowResult para propagação graciosa de erros
  7. Design de biblioteca de iteradores de nível de produção — Alocação zero, compostos, termináveis

Iteradores não são substitutos para canais ou slices. Eles são um novo membro do kit de ferramentas de processamento de dados do Go — quando você precisa de avaliação preguiçosa e composição de pipeline com alocação zero, iteradores são a melhor escolha.

Leitura Relacionada:

Experimente estas ferramentas executadas localmente no navegador — nenhum cadastro necessário →

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