Go 1.24 Iterator-Muster: 7 Produktionsmuster von Range Over Func bis Daten-Pipelines

编程语言(Aktualisiert am 14. Juli 2026)

Wenn for-Schleifen auf Funktionen treffen: Go's Iterator-Paradigmenwechsel

Letzte Woche habe ich einen Datenverarbeitungsdienst mit 3 Ebenen verschachtelter for-Schleifen refaktoriert, der 100K Datensätze verarbeitete — der Speicher stieg auf 2GB, weil jede Ebene Zwischenergebnisse als Slices speichern musste, bevor sie an die nächste übergeben wurden. Nach dem Wechsel zu einer Iterator-Pipeline sank der Speicher auf 15MB und die Verarbeitung war 30% schneller. Der entscheidende Wechsel: kein „sammeln dann verarbeiten" mehr — stattdessen „iterieren und sofort verarbeiten".

Go 1.24 hat die range over func-Syntax und das iter-Paket offiziell stabilisiert und gibt Go damit native, zuweisungsfreie, komponierbare Iteratoren. Das ist kein syntaktischer Zucker — es ist ein grundlegender Wechsel im Datenverarbeitungsparadigma. Dieser Artikel behandelt 7 produktionsreife Go-Iterator-Muster, die Ihnen helfen, effiziente, elegante, komponierbare Daten-Pipelines zu erstellen.


Kernpunkte

  • range over func ist die zentrale Iterator-Syntax: Go 1.24 ermöglicht es, Funktionen direkt zu durchlaufen
  • Push/Pull-Iterator-Konvertierung: Beide Richtungen verstehen, die iter.Pull-Konvertierung beherrschen
  • Daten-Pipeline-Komposition: Map/Filter/Reduce-Kettenkomposition ohne Zwischenzuweisungen
  • Lazy Evaluation und unendliche Folgen: Bei Bedarf berechnen, unendliche Datenströme verarbeiten
  • Nebenläufige Iteratoren und Fan-out: Mehrere Goroutinen konsumieren Iteratoren parallel
  • Iterator-Fehlerbehandlung: Fehler während der Iteration elegant behandeln
  • Produktionsreifes Iterator-Bibliotheksdesign: Wiederverwendbare Iterator-Toolkits erstellen

Inhaltsverzeichnis

  1. Go-Iterator-Kernkonzepte Referenz
  2. Muster 1: range over func Basis-Iterator
  3. Muster 2: Push/Pull-Iterator-Konvertierung
  4. Muster 3: Daten-Pipeline-Komposition (Map/Filter/Reduce)
  5. Muster 4: Lazy Evaluation und unendliche Folgen
  6. Muster 5: Nebenläufige Iteratoren und Fan-out
  7. Muster 6: Iterator-Fehlerbehandlung
  8. Muster 7: Produktionsreifes Iterator-Bibliotheksdesign
  9. 5 Häufige Fallstricke und Lösungen
  10. 10 Häufige Fehler und ihre Behebung
  11. Erweiterte Optimierungstipps
  12. Vergleichsanalyse: Iteratoren vs. Channels vs. Slices
  13. Empfohlene Online-Tools
  14. Zusammenfassung

Go-Iterator-Kernkonzepte Referenz

Konzept Signatur Zweck Beispiel
Iteratorfunktion func(yield func(V) bool) Einwert-Iterator func(yield func(int) bool)
Schlüssel-Wert-Iterator func(yield func(K, V) bool) Schlüssel-Wert-Paar-Iteration func(yield func(int, string) bool)
Pull-Iterator func() (V, bool) Bedarfsgesteuertes Abrufen next, stop := iter.Pull(seq)
iter.Pull func(Seq[V]) (func() (V, bool), func()) Push-zu-Pull-Konvertierung Konsumentengesteuerte Traversierung
iter.Stop Eingebaute Stop-Funktion Frühzeitige Beendigung stop() gibt Ressourcen frei
yield-Rückgabewert bool Iterationsfortsetzung/-stopp steuern yield(v) gibt false zum Stoppen zurück
Lazy Evaluation Verzögerte Berechnung Werte bei Bedarf erzeugen Unendliche Folgen, Dateizeilen
Pipeline-Komposition Funktionsverkettung Keine Zwischenzuweisungen Filter(Map(Seq, fn), pred)

Muster 1: range over func Basis-Iterator

Problem: Speicherfallen des traditionellen Durchlaufs

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 Million Benutzer? 1 Million User-Structs in den Speicher geladen. Sie brauchen nur die ersten 10? Pech — erst alle laden.

Lösung: range over func Iterator

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

Verwendung:

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

Wenn break ausgeführt wird, gibt yield false zurück und die Iteratorfunktion kehrt sofort zurück — nur 10 Zeilen abgefragt, Datenbankverbindung ordnungsgemäß geschlossen.

Iterator-Ausführungsablauf

┌─────────────┐     yield(v)      ┌──────────────┐
│  Iterator    │ ──────────────→  │   range-Schleife │
│  (Produzent)  │                   │  (Konsument)  │
│              │  ←──────────────  │              │
│              │   yield liefert   │              │
│              │      bool zurück  │              │
└─────────────┘                   └──────────────┘
      │                                  │
      │  yield liefert false → return    │
      │  (frühzeitige Beendigung)        │
      └──────────────────────────────────┘

Einwert- vs. Schlüssel-Wert-Iterator

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

Muster 2: Push/Pull-Iterator-Konvertierung

Unterschiede zwischen Push- und Pull-Iteratoren

Push-Iterator (iter.Seq)            Pull-Iterator (func() (V, bool))
┌──────────────────┐               ┌──────────────────┐
│  Produzent schiebt │               │  Konsument zieht   │
│  yield(v) → Kons.  │               │  next() → Prod.    │
│                    │               │                    │
│  Gut für: range    │               │  Gut für: manuelle  │
│  Gut für: Pipelines│               │  Gut für: peek      │
│  Gut für: lazy     │               │  Gut für: Interop   │
└──────────────────┘               └──────────────────┘
         │                                  │
         │      iter.Pull()-Konvertierung   │
         └──────────────────────────────────┘

iter.Pull-Konvertierung verwenden

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("Frühzeitige Beendigung")
            break
        }
    }
}

Praktische Pull-Iterator-Anwendungen: Peek und 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))

Wichtig: iter.Pull Ressourcenbereinigung

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("Erste Zeile:", v)
    }
}

defer stop() stellt sicher, dass die Datei ordnungsgemäß geschlossen wird, auch wenn nur ein Wert konsumiert wird.


Muster 3: Daten-Pipeline-Komposition (Map/Filter/Reduce)

Problem: Verschachtelte Schleifen und Zwischen-Slices

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 Durchläufe, 2 Zwischen-Slices. Bei großen Datensätzen sind Speicher- und GC-Belastung erheblich.

Lösung: Iterator-Pipeline

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
}

Pipeline-Komposition verwenden

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("Gesamt: %.2f\n", total)
}

Keine Zwischenzuweisungen: Filter, Map und Reduce sind verkettet — jedes Element wird genau einmal verarbeitet.

Pipeline-Ausführungsablauf

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

Weitere Pipeline-Operatoren

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

Muster 4: Lazy Evaluation und unendliche Folgen

Problem: Verschwendung durch Vorab-Berechnung aller Ergebnisse

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
}

Brauchen Sie die ersten 10 Fibonacci-Zahlen? Sie müssen n angeben. Wissen nicht wie viele? Vorab einen „groß genug" berechnen.

Lösung: Unendlicher Iterator + Lazy Evaluation

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

Lazy-Folgen verwenden

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

Lazy-Dateiverarbeitung

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

10GB Log-Datei? Nur die ersten 100 ERROR-Zeilen lesen — nahezu kein Speicherverbrauch.


Muster 5: Nebenläufige Iteratoren und Fan-out

Problem: Leistungsengpass bei einsträngigen Iteratoren

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

1000 Bilder, je 100ms, insgesamt 100 Sekunden. CPU-Auslastung nur 12,5% (1 von 8 Kernen genutzt).

Lösung: Fan-out nebenläufiger Iterator

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

Nebenläufige Iteratoren verwenden

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

Architektur nebenläufiger Iteratoren

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

Muster 6: Iterator-Fehlerbehandlung

Problem: Verschluckte Fehler in Iteratoren

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

Wenn decoder.Decode fehlschlägt, geht der Fehler vollständig verloren. Der Aufrufer kann nicht unterscheiden, ob die Iteration normal endete oder aufgrund eines Fehlers.

Lösung: Iteratoren mit Fehlerweitergabe

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

Praktische Anwendung: Datenbankzeilen-Iterator

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("Iterationsfehler: %v", r.Err)
            break
        }
        fmt.Printf("%s: %.2f€\n", r.Value.Name, r.Value.Price)
    }
}

Fehlerweitergabe-Pipeline

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

Muster 7: Produktionsreifes Iterator-Bibliotheksdesign

Designprinzipien

┌─────────────────────────────────────────────┐
│      Produktions-Iterator-Bibliotheksprinzipien  │
├─────────────────────────────────────────────┤
│  1. Keine Zuweisungen: keine Zwischen-Slices │
│  2. Komponierbar: alle Ops geben iter.Seq zurück │
│  3. Beendbar: Freigabe bei yield=false       │
│  4. Beobachtbar: Fehlerweitergabe & Metriken │
│  5. Testbar: reine Funktionen, keine Seiteneffekte│
└─────────────────────────────────────────────┘

Vollständiges Iterator-Toolkit

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
}

Verwendungsbeispiele

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("Anzahl gerader Zahlen:", evenCount)

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

5 Häufige Fallstricke und Lösungen

Fallstrick 1: Schleifenvariablen in Iteratoren erfassen

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
}

Alle Iteratoren geben 3 zurück. i wird durch Closure erfasst, der Wert ist 3 wenn die Schleife endet.

Fix:

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
}

Fallstrick 2: Vergessen, stop aufzurufen, verursacht Ressourcenlecks

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

Die Datei wird nie geschlossen.

Fix:

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

Fallstrick 3: Iteratoren sind nicht reentrant

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

Der zweite range-Aufruf gibt nichts aus. Iteratoren sind einmalig verwendbar.

Fix:

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

Fallstrick 4: Panic in Iteratoren

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

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

In Go 1.24 können Panics von range over func extern wiederhergestellt werden. Aber verlassen Sie sich nicht auf dieses Verhalten — Iteratoren sollten ihre eigenen Fehler behandeln.

Fallstrick 5: Nebenläufiger Range über denselben Iterator

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 ist nicht nebenläufigkeitssicher. Mehrere Goroutinen, die gleichzeitig iterieren, verursachen Datenrennen.

Fix: Verwenden Sie das Fan-out-Muster oder erstellen Sie unabhängige Iteratoren für jede Goroutine.


10 Häufige Fehler und ihre Behebung

Fehler Ursache Lösung
cannot range over seq (variable of type func(yield func(int) bool)) Funktionssignatur stimmt nicht mit iter.Seq überein Sicherstellen, dass die Signatur func(yield func(V) bool) ist
cannot use function as type iter.Seq[int] Yield-Funktionsparametertyp stimmt nicht überein Yield-Parametertyp mit Seq-Typparameter prüfen
iter.Pull: iterator did not call stop Pull-Iterator-Stop nicht aufgerufen Immer defer stop() verwenden
panic: range over func: yield called after return yield nach Iterator-Return aufgerufen Prüfen, ob Goroutinen yield nach Return aufrufen
deadlock Fan-out inputCh/outputCh nicht geschlossen Sicherstellen, dass alle Goroutinen beendet werden, bevor Channels geschlossen werden
data race Mehrere Goroutinen durchlaufen dieselbe Seq Jede Goroutine verwendet einen unabhängigen Iterator
out of memory Unendlicher Iterator ohne Take Immer Take/Skip mit unendlichen Folgen verwenden
goroutine leak Goroutinen im Iterator beenden sich nie context oder done-Channel zur Beendigungssteuerung verwenden
unexpected EOF during iteration Datei während der Dateiiteration geändert Dateisperren oder Snapshots verwenden
yield returns false but iteration continues Yield-Rückgabewert nicht geprüft Yield-Rückgabe nach jedem Aufruf prüfen, bei false zurückkehren

Erweiterte Optimierungstipps

Tipp 1: Vorab-Zuweisung zur Reduzierung der GC-Belastung

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

Wenn Sie die ungefähre Anzahl kennen, vorab zuweisen, um mehrfache Größenänderungen zu vermeiden.

Tipp 2: Batch-Iterator zur Reduzierung von Systemaufrufen

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

Für Datenbank-Batch-Inserts alle 100 Zeilen committen, um Netzwerk-Roundtrips zu reduzieren.

Tipp 3: Iterator + Context zur Zeitsteuerung

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

Vergleichsanalyse: Iteratoren vs. Channels vs. Slices

Dimension Iterator (iter.Seq) Channel (chan) Slice ([]T)
Speicherverbrauch O(1) O(n) Puffer O(n)
Lazy Evaluation Ja Nein Nein
Unendliche Folgen Ja Nein Nein
Nebenläufigkeitssicher Nein Ja Nein
Komponierbarkeit Ausgezeichnet (Funktionskette) Mittel (benötigt Goroutine) Schlecht (Zwischen-Slices)
Fehlerbehandlung Erfordert Wrapping Native Unterstützung Direkte Fehlerrückgabe
Reentrant Nein Nein Ja
Leistung Keine Zuweisungen Lock-Overhead Kopier-Overhead
Anwendungsfall Daten-Pipelines, Lazy Eval Nebenläufige Kommunikation Kleine Datensätze, Wahlfreier Zugriff
Go-Version 1.24+ 1.0+ 1.0+
Debugging-Schwierigkeit Mittel Hoch Niedrig

Entscheidungsbaum

Lazy Evaluation oder unendliche Folgen benötigt?
├── Ja → Iterator
└── Nein
    ├── Nebenläufige Kommunikation benötigt?
    │   └── Ja → Channel
    └── Nein
        ├── Kleine Daten + wahlfreier Zugriff?
        │   └── Ja → Slice
        └── Nein → Iterator

Empfohlene Online-Tools

Externe Referenzen


Zusammenfassung

Go 1.24 Iterator-Muster geben Go native, zuweisungsfreie, komponierbare Daten-Pipeline-Fähigkeiten. Die 7 Kernmuster decken das gesamte Spektrum vom einfachen Durchlauf bis zum produktionsreifen Bibliotheksdesign ab:

  1. range over func Basis-Iterator — Der Ausgangspunkt, yield steuert den Ablauf
  2. Push/Pull-Iterator-Konvertierung — iter.Pull ermöglicht manuelle Steuerung
  3. Daten-Pipeline-Komposition — Map/Filter/Reduce-Verkettung ohne Zwischenzuweisungen
  4. Lazy Evaluation und unendliche Folgen — Bei Bedarf berechnen, unendliche Ströme verarbeiten
  5. Nebenläufige Iteratoren und Fan-out — Paralleler Konsum mit mehreren Goroutinen
  6. Iterator-Fehlerbehandlung — RowResult-Muster für elegante Fehlerweitergabe
  7. Produktionsreifes Iterator-Bibliotheksdesign — Keine Zuweisungen, komponierbar, beendbar

Iteratoren sind kein Ersatz für Channels oder Slices. Sie sind ein neues Mitglied in Go's Datenverarbeitungs-Toolkit — wenn Sie Lazy Evaluation und zuweisungsfreie Pipeline-Komposition benötigen, sind Iteratoren die beste Wahl.

Weiterführende Lektüre:

Probiere diese browser-lokalen Tools aus — keine Registrierung erforderlich →

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