0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Goのイテレーター・ジェネレータについて

Last updated at Posted at 2026-01-23

はじめに

Go 1.23でイテレータ機能が標準ライブラリに追加されました。
本記事では、新しく導入されたiterパッケージの使い方と、従来のスライスベースの反復処理との違いについて、実行フローとパフォーマンスについてまとめていきます。

イテレータとは

イテレータは、コレクションの要素を順次走査するための抽象化です。
Goでは従来からrangeを用いたスライスの反復処理が可能でしたが、Go1.23からは関数ベースのカスタムイテレータが言語レベルでサポートされるようになりました。

// 従来のスライスベースの反復処理
for i, v := range []string{"a", "b", "c"} {
    fmt.Println(i, v)
}

ジェネレータの概要

ジェネレータは、値を遅延評価的に生成するイテレータの一種です。Pythonのyieldキーワードのような専用構文はありませんが、iterパッケージで定義された型を使って実現します。

iter パッケージの型定義

package iter

type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)
  • Seq[V]
    • 単一の値を返すイテレータ
  • Seq2[K, V]
    • キーと値のペアを返すイテレータ(mapのrangeループに相当)

yield関数の戻り値は継続フラグで、falseを返すとイテレーションが中断されます。

スライス vs ジェネレータ

スライスとジェネレータの実装について比較していきます。

【パターン1】 スライスベースの実装

func Test_Slice(t *testing.T) {
    strings := createSlice(5)
    for _, s := range strings {
        fmt.Printf("Test_Slice: %s\n", s)
    }
}

func createSlice(max int) []string {
    slice := make([]string, 0, max)
    for i := range max {
        fmt.Printf("createSlice: %d\n", i)
        slice = append(slice, strconv.Itoa(i))
    }
    return slice
}

実行結果:

createSlice: 0
createSlice: 1
createSlice: 2
createSlice: 3
createSlice: 4
Test_Slice: 0
Test_Slice: 1
Test_Slice: 2
Test_Slice: 3
Test_Slice: 4

スライス生成が完全に完了してから、rangeループによる反復処理が開始されます。

【パターン2】 ジェネレータベースの実装

func Test_Yield(t *testing.T) {
    stringGenerator := generateString(5)
    for s := range stringGenerator {
        fmt.Printf("Test_Yield: %s\n", s)
    }
}

func generateString(max int) iter.Seq[string] {
    return func(yield func(string) bool) {
        for i := range max {
            fmt.Printf("generateString: %d\n", i)
            if !yield(strconv.Itoa(i)) {
                return
            }
        }
    }
}

実行結果:

generateString: 0
Test_Yield: 0
generateString: 1
Test_Yield: 1
generateString: 2
Test_Yield: 2
generateString: 3
Test_Yield: 3
generateString: 4
Test_Yield: 4

生成と処理が交互に実行されています。これが遅延評価の特徴です。

実行フローの違い

スライスの場合

  1. createSliceが全要素を生成
  2. スライスがメモリ上に確保される
  3. rangeが各要素を順次処理

ジェネレータの場合

  1. rangeiter.Seq型の関数を実行
  2. yieldが呼ばれるたびにループ本体が実行される
  3. 次の要素が必要になるまで生成処理は進まない

注目すべきは、generateStringの戻り値である関数を明示的に呼び出していない点です。rangeキーワードがiter.Seq型を検出すると、自動的に関数を実行してイテレーションを開始します。

パフォーマンス特性の比較

項目 スライス ジェネレータ
メモリ使用量 O(n) 全要素を保持 O(1) 現在の状態のみ
初期化コスト 高い - 全要素を事前生成 低い - 遅延生成
反復処理速度 高速 - メモリアクセスのみ やや低速 - 毎回関数呼び出し
CPU使用率 低い(反復時) 高い(関数呼び出しオーバーヘッド)
早期終了時の効率 無駄な生成が発生 必要な分だけ生成

ベンチマーク例

func BenchmarkSlice(b *testing.B) {
    for i := 0; i < b.N; i++ {
        slice := createSlice(1000)
        for _, s := range slice {
            _ = s
        }
    }
}

func BenchmarkGenerator(b *testing.B) {
    for i := 0; i < b.N; i++ {
        gen := generateString(1000)
        for s := range gen {
            _ = s
        }
    }
}

使い分けの指針

スライスを選ぶべきケース

  • 全要素を複数回走査する必要がある
  • データサイズが小さく、メモリに余裕がある
  • 反復処理のパフォーマンスが重要
  • データを一度に取得するコストが低い

ジェネレータを選ぶべきケース

  • データサイズが大きく、メモリ効率が重要
  • 要素生成のコストが高い(DB クエリ、API コールなど)
  • 早期終了の可能性が高い(条件に合う最初の要素を探すなど)
  • 無限シーケンスを扱う場合

実践例:無限シーケンス

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

// 最初の10個だけ取得
func Test_InfiniteCounter(t *testing.T) {
    count := 0
    for n := range infiniteCounter() {
        fmt.Println(n)
        count++
        if count >= 10 {
            break
        }
    }
}

このようなパターンはスライスでは実現できません。

まとめ

Go1.23のイテレータ機能は、従来のスライスベースの反復処理に加えて、メモリ効率の良い遅延評価を実現できます。

  • スライス
    メモリと引き換えに高速な反復処理

  • ジェネレータ
    CPU時間と引き換えにメモリ効率の良い遅延評価

適切なパターンを選択することで、パフォーマンスとリソース使用量のバランスを最適化してきましょう。

参考資料

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?