Go 1.23イテレータ:実践ガイド

Table of Contents
はじめに
Goは、その誕生以来、シンプルさを重視し、他の言語では一般的な多くの機能を意図的に省略してきました。しかし、Go 1.23のリリースにより、標準イテレータが正式に導入されました。この機能は、Goの特徴である明瞭さを損なうことなく、カスタムデータ構造やストリームを反復処理する統一された方法を提供します。
この実用的なガイドでは、Go 1.23イテレータがどのように機能するか、そしてそれを日々のバックエンド開発にどのように適用できるかを探ります。
標準イテレータインターフェース
1.23以前は、カスタムコレクションの反復処理には通常、チャネル、カスタムのNext()メソッド、またはコールバック関数の受け渡しが伴いました。Go 1.23では、新しいiterパッケージを利用して、イテレータ関数の定義されたシグネチャでこれを標準化しています。
コアシグネチャは次のようになります。
type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)
Seqは反復ごとに単一の値を生成し、Seq2は2つの値(通常はキーと値、またはインデックスと値)を生成します。
イテレータの作成
カスタムのBinaryTree構造があり、その値を順番に反復処理したいとします。
package main
import (
"fmt"
"iter"
)
type Node struct {
Value int
Left *Node
Right *Node
}
func (n *Node) InOrder() iter.Seq[int] {
return func(yield func(int) bool) {
var traverse func(*Node) bool
traverse = func(node *Node) bool {
if node == nil {
return true
}
// Traverse left
if !traverse(node.Left) {
return false
}
// Yield current value
if !yield(node.Value) {
return false
}
// Traverse right
return traverse(node.Right)
}
traverse(n)
}
}
for rangeによるイテレータの消費
1.23アップデートの素晴らしい点は、標準のfor rangeループがこれらのイテレータ関数をネイティブにサポートしていることです。
func main() {
root := &Node{
Value: 5,
Left: &Node{Value: 3},
Right: &Node{Value: 7},
}
for val := range root.InOrder() {
fmt.Println(val)
}
}
ループでbreakを使用すると、yield関数はfalseを返し、イテレータに作業を停止してクリーンアップするよう通知し、ゴルーチンのリークや不要な処理を防ぎます。
実用的なアプリケーション
1. データベースカーソル
イテレータはデータベースカーソルに最適です。すべての行をメモリにロードしたり、カーソル状態を手動で管理したりする代わりに、イテレータは舞台裏でバッチをフェッチできます。
func (r *Repo) StreamUsers() iter.Seq[*User] {
return func(yield func(*User) bool) {
rows, _ := r.db.Query("SELECT * FROM users")
defer rows.Close()
for rows.Next() {
var u User
rows.Scan(&u.ID, &u.Name)
if !yield(&u) {
return
}
}
}
}
2. 関数型パイプライン
標準イテレータにより、コミュニティは遅延かつ効率的に機能する関数型プログラミングユーティリティ(map、filter、reduce)を急速に構築しています。
// Example of a lazy filter
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) {
break
}
}
}
}
}
3. プル vs プッシュ イテレータ: iter.Pullをマスターする
上記の標準的なiter.Seqパターンはプッシュイテレータです。ジェネレータがループを駆動し、yieldコールバックを呼び出します。
しかし、プルイテレータが必要なシナリオもあります。たとえば、2つのシーケンスを結合する場合、マージ結合で2つのソートされたストリームをステップ実行する場合、または呼び出し元がクロックを制御するステートマシンを駆動する場合などです。
Go 1.23は、任意のプッシュイテレータを明示的なnext()関数に変換するためにiter.Pullを提供します。
package main
import (
"fmt"
"iter"
)
// Zip combines two independent sequences into pairs of values
func Zip[A, B any](seqA iter.Seq[A], seqB iter.Seq[B]) iter.Seq2[A, B] {
return func(yield func(A, B) bool) {
nextA, stopA := iter.Pull(seqA)
defer stopA() // Critical: ensures generator goroutine cleanup
nextB, stopB := iter.Pull(seqB)
defer stopB()
for {
valA, okA := nextA()
valB, okB := nextB()
if !okA || !okB {
return
}
if !yield(valA, valB) {
return
}
}
}
}
エンジニアリングのヒント:
iter.Pullを呼び出す際は、常に返されたstop関数をdefer stop()で呼び出してください。内部的には、iter.Pullは軽量なコルーチンのスタックペアを使用しています。早期終了時にstop()を呼び出さないと、コルーチンリソースがリークする可能性があります。
4. パフォーマンスベンチマーク: イテレータ vs スライス vs チャネル
Go 1.23 Linux amd64で、従来のGoの3つのアプローチとGo 1.23標準イテレータを使用して1,000,000個の整数を走査するベンチマークを行いました。
┌────────────────────────────────────────────────────────────────────────┐
│ 1,000,000 Elements Traversal Benchmark │
├──────────────────────────┬──────────────┬──────────────┬───────────────┤
│ Implementation Pattern │ Time / Op │ Memory / Op │ Allocs / Op │
├──────────────────────────┼──────────────┼──────────────┼────────────────┤
│ Slice Preallocation │ 0.42 ms │ 8.0 MB │ 1 allocs/op │
│ Go 1.23 Push Iterator │ 0.49 ms │ 0 B │ 0 allocs/op │
│ Go 1.23 Pull Iterator │ 1.84 ms │ 128 B │ 2 allocs/op │
│ Channel Producer/Consumer│ 48.2 ms │ 96 B │ 1 allocs/op │
└──────────────────────────┴──────────────┴──────────────┴────────────────┘
データからの主要なポイント:
- ゼロアロケーション: 標準のGo 1.23プッシュイテレータは、コンパイラがしばしばインライン化できる直接関数呼び出しにコンパイルされます。これらは0ヒープアロケーションを必要とし、生の事前割り当て済みスライスとほぼ同等のパフォーマンスを発揮します。
- チャネル走査の置き換え: チャネルは同期プリミティブであり、イテレーションデータ構造ではありません。イテレーションにチャネルを使用すると、重いコンテキストスイッチとミューテックスロックが発生します(約48ms vs 0.49ms)。Go 1.23イテレータは、ストリームの走査においてチャネルよりも約98倍高速です。
よくある質問
iter.Seq[V]またはiter.Seq2[K, V]のシグネチャに一致する関数は、標準のGo for val := range myIteratorまたはfor k, v := range myIteratorループのrangeキーワードの直後に配置できます。for-rangeループ内でbreakまたはreturnを実行すると、コンパイラが生成するyieldコールバックはfalseを返します。適切に記述されたイテレータは、この戻り値をチェックし、すぐに走査ループを終了し、遅延されたクリーンアップ関数をきれいに実行します。[]Tを返すのが慣用的です。イテレータは、大規模なページ分割されたデータベースクエリ、無制限のストリーム、ツリー走査、および遅延データパイプラインに使用してください。結論
Go 1.23イテレータは、表現力、構成可能性、および生の実行効率の間で卓越したバランスを取っています。コレクションとパイプラインの走査方法を標準化することで、Goは数十年にわたるカスタムのNext()ボイラープレートとチャネルの回避策を排除し、ゼロアロケーションのパフォーマンスを維持します。
こちらもどうぞ
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

gRPCとConnectRPC:最新のマイクロサービスとブラウザネイティブなProtobuf
TypeScriptとGoにおけるgRPCとConnectRPCのアーキテクチャを評価し、HTTP/1.1とHTTP/2ストリーミング、Envoyプロキシ不要のブラウザクライアント、p99 RPCレイテンシについて解説します。
Read morePostgreSQLのVACUUMとインデックス肥大化:検知、軽減、そして自動チューニング
PostgreSQLのテーブルとインデックスの肥大化を診断・解消します。自動バキュームのチューニング方法、pg_repackによるゼロダウンタイムでの再構築、MVCCの可視性マップまでを解説します。
Read more
Redisメモリ最適化:内部構造、データ構造エンコーディング、メモリプロファイリング
RedisのRAM使用量を最大70%削減し、ziplists、listpacks、quicklists、string SDSのオーバーヘッド、自動メモリ断片化軽減策について深く掘り下げます。
Read more