Share
Embed
range over func 2年間の軌跡 Issue #56413 はGoのエコシステムをどう変えたか
Go Conference2026にて登壇した内容になります
Ryuji Kokubu
September 11, 2026
Transcript
89 slides
-
range over func 2年間の軌跡 Issue #56413 はGoのエコシステムをどう変えたか 合同会社DMM.com / オンラインサロン開発部
國分竜二 2026 / 09 / 11 © DMM.com -
自己紹介 • 合同会社DMM.com オンライン サロン開発部 • 今日ブースも出してるのでぜひ 遊びに来てください(もう終わり ですが...) •
X: @_ryuji_cre8ive © DMM.com -
改めて復習 • range over func とは? • 関数値を range で反復する仕
組み • Go 1.23 で正式導入 • iter.Seq / iter.Seq2 を range で 反復 for v := range seq { use(v) } for k, v := range seq2 { use(k, v) } © DMM.com -
range over func 2年間の軌跡 Issue #56413 はGoのエコシステムをどう変えたか 合同会社DMM.com / オンラインサロン開発部
國分竜二 2026 / 09 / 11 © DMM.com -
この3つ、なぜ全部シグネチャが違うんでしょう? filepath.Walk( func(path string, info fs.FileInfo, err error) error, )
sync.Map.Range( func(key, value any) bool, ) flag.Visit( func(f *flag.Flag), ) © DMM.com -
この3つ、なぜ全部シグネチャが違うんでしょう? filepath.Walk( func(path string, info fs.FileInfo, err error) error, )
sync.Map.Range( func(key, value any) bool, ) flag.Visit( func(f *flag.Flag), ) © DMM.com どれもとあるコレクションを走 査するメソッドです -
この3つ、なぜ全部シグネチャが違うんでしょう? filepath.Walk( func(path string, info fs.FileInfo, err error) error, )
やりたいことは同じ。 func(key, それなのに呼び出し方と戻り値はバラバラ value any) bool, sync.Map.Range( ) flag.Visit( func(f *flag.Flag), ) © DMM.com どれもとあるコレクションを走 査するメソッドです -
標準ライブラリの走査APIは、形が揃っていない 共通の interface はなく、呼び出し方も戻り値もバラバラ • • • • • Frames.Next
(T, bool) Scanner.Scan bool sql.Rows.Next bool + Err filepath.Walk(root, fn) sync.Map.Range(fn) © DMM.com -
同じ (T, bool) でも意味が違う // 多くの慣習: bool は「Tが有効かどうか」 v, ok
:= m[key] // okがtrueならvは有効 // runtime.Frames.Next は違う frame, more := frames.Next() // more は「次の呼び出しが有効な値を返すか」 © DMM.com -
同じ (T, bool) でも意味が違う // 多くの慣習: bool は「Tが有効かどうか」 v, ok
:= m[key] // okがtrueならvは有効 // runtime.Frames.Next は違う frame, more := frames.Next() // more は「次の呼び出しが有効な値を返すか」 同じ形をしていても、中身の意味は関数ごとに学び直す必要があった。 © DMM.com -
2022年10月25日、議論が動き出す Discussion #56413 なぜ動き出したか “user-defined iteration using range over func
values” Goにはシーケンシャルなデータを反復処理する標 © DMM.com 準的な方法がなかった。各実装が個別最適化し た結果、Goユーザーのキャッチアップコストが増 大していた。 -
実験から正式リリースまでの時系列 2022.10 Discussion #56413 オープン © DMM.com → 2023.07 Issue
#61405 提案提出 → Go 1.22 GOEXPERIMENT=rangefunc で試験導入 → Go 1.23 2024.08 正式リリース 言語機能として有効化 -
設計の決断 みなさんなら、 interfaceで解決しませんか? type Iterator[T any] interface { Next() (T,
bool) } // でもGoチームはこの道を選ばなかった © DMM.com -
設計の決断 みなさんなら、 interfaceで解決しませんか? type Iterator[T any] interface { Next() (T,
bool) } // でもGoチームはこの道を選ばなかった 答えは、言語仕様そのものへの組み込みでした。 © DMM.com -
Issue #61405 — rsc、2023年7月17日 “spec: add range over int, range
over func” Discussion #56413 の議論を受けて、 for-range で整数と関数を扱えるようにする 言語拡張として提案が出された © DMM.com 11 -
Issue #61405 — rsc、2023年7月17日 “spec: add range over int, range
over func” Discussion #56413 の議論を受けて、 for-range で整数と関数を扱えるようにする 言語拡張として提案が出された push-iteratorのみを言語サポートの 対象にし、pull-iteratorへの変換は 別の標準ライブラリ関数に委ねる、 という役割分担を明確化した。 出典 : golang/go©Issue #61405 DMM.com 11 -
Issue #61405 — rsc、2023年7月17日 “spec: add range over int, range
over func” Push? Pull? Discussion #56413 の議論を受けて、 for-range で整数と関数を扱えるようにする 言語拡張として提案が出された push-iteratorのみを言語サポートの 対象にし、pull-iteratorへの変換は 別の標準ライブラリ関数に委ねる、 という役割分担を明確化した。 出典 : golang/go©Issue #61405 DMM.com 11 -
Push と Pull:誰が次の1件を主導するか Pull Iterator Push Iterator yieldを呼び出し、値を mainにpush main側が明示的に
次の値を呼び出す next関数を実行 © DMM.com -
Push と Pull:誰が次の1件を主導するか Pull Iterator Push Iterator Push / Pullは「値の向き」ではなく
yieldを呼び出し、値を iterationの主導権がどちらにあるかを表します mainにpush main側が明示的に 次の値を呼び出す next関数を実行 © DMM.com -
range over funcは、どうやって動く? “For a function f, the iteration proceeds
by calling f with a synthesized yield function that invokes the body of the loop.” 関数 f の反復は、ループ本体を呼び出す合成された yield 関数を渡して、f を呼び出すことで進む。 © DMM.com -
range over funcは、どうやって動く? “For a function f, the iteration proceeds
by calling f with a synthesized yield function that invokes the body of the loop.” 関数 f の反復は、ループ本体を呼び出す合成された yield 関数を渡して、f を呼び出すことで進む。 コンパイラが yield関数を合成し、 range本体へ渡している。 © DMM.com -
rangeの中のdeferは、いつ実行される? for v := range seq { defer cleanup(v) …
} © DMM.com 1回ごとにdefer? -
rangeの中のdeferは、いつ実行される? for v := range seq { defer cleanup(v) …
} 1回ごとにdefer? 答え:外側の関数が returnするとき。他の rangeループと同じ © DMM.com -
固定回数のループ・逆順走査 BEFORE for i := 0; i < n; i++
{ /* ... */ } for i := n-1; i >= 0; i-- { _ = s[i] } © DMM.com -
固定回数のループ・逆順走査 BEFORE for i := 0; i < n; i++
{ /* ... */ } ①よくあるforループ for i := n-1; i >= 0; i-- { _ = s[i] } © DMM.com -
固定回数のループ・逆順走査 BEFORE for i := 0; i < n; i++
{ /* ... */ } ①よくあるforループ for i := n-1; i >= 0; i-- { _ = s[i] } © DMM.com ②逆順にした途端 あれ、条件は? イテレーションの向きは? -
固定回数のループ・逆順走査 BEFORE for i := 0; i < n; i++
{ /* ... */ } ①よくあるforループ パニック for i := n-1; i >= 0; i-- { _ = s[i] } © DMM.com ②逆順にした途端 あれ、条件は? イテレーションの向きは? -
固定回数のループ・逆順走査 BEFORE AFTER for i := 0; i < n;
i++ { /* ... */ } for i := range n { for i := n-1; i >= 0; i-- { _ = s[i] } for i, v := range © DMM.com // ... } slices.Backward(s) { // ... } -
固定回数のループ・逆順走査 BEFORE AFTER for i := 0; i < 便利な関数
n; i++ { /* ... */ } Backward登場 for i := range n { for i := n-1; i >= 0; i-- { _ = s[i] } for i, v := range © DMM.com // ... } slices.Backward(s) { // ... } -
固定回数のループ・逆順走査 BEFORE for i := 0; i < 便利な関数 n;
i++ { /* ... */ } Backward登場 for i := n-1; i >= 0; i-- { _ = s[i] } © DMM.com AFTER for i := range n { // ... �� } for i, v := range slices.Backward(s) { // ... } -
maps.Keys / Values は、なぜ iterator になった? 2021.07 #47330 maps 構想
Keys / Values →sliceを返す © DMM.com -
maps.Keys / Values は、なぜ iterator になった? 2021.07 #47330 2022.12 #57436
maps 構想 Keys / Values →sliceを返す Go 1.21向けに x/exp/maps 標準化を提案 © DMM.com -
maps.Keys / Values は、なぜ iterator になった? iter構想 2021.07 #47330 2022.12
#57436 maps 構想 Keys / Values →sliceを返す Go 1.21向けに x/exp/maps 標準化を提案 © DMM.com -
Go 1.21でslice版を出すのは早い? Go 1.22でiteratorを返す可能性が高いなら、 Go 1.21でsliceを返す形で出すのは短絡的では ないか。 いったん取り下げるべきでは? 将来、iteratorとしてMap /
Filter / Reduceと 組み合わせる可能性がある。 sliceが必要なら slices.Collect で変換できる。 © DMM.com -
maps.Keys / Values は、なぜ iterator になった? iter構想 2021.07 #47330 2022.12
#57436 2023.07 #61538 maps 構想 Keys / Values →sliceを返す Go 1.21向けに x/exp/maps 標準化を提案 slice版を見送り © DMM.com KeysSlice案も 不採用#61626 -
maps.Keys / Values は、なぜ iterator になった? iter構想 2021.07 #47330 2022.12
#57436 2023.07 #61538 2023.08 #61900 maps 構想 Keys / Values →sliceを返す Go 1.21向けに x/exp/maps 標準化を提案 slice版を見送り iter.Seqを追加 Keys / Values Go 1.23 © DMM.com KeysSlice案も 不採用#61626 -
maps.Keys / Values は、なぜ iterator になった? iter構想 2021.07 #47330 2022.12
#57436 2023.07 #61538 2023.08 #61900 maps 構想 Keys / Values →sliceを返す Go 1.21向けに x/exp/maps 標準化を提案 slice版を見送り iter.Seqを追加 Keys / Values Go 1.23 KeysSlice案も 不採用#61626 共通のiteration protocolができたことで、 APIの戻り値だけでなく、名前の使い方にまで影響を及ぼした © DMM.com -
パフォーマンスはどう変わったか(実測) 共通条件:1M items / Go 1.23.1 / M4 Pro 全件処理(時間
/ メモリ/ 確保回数) 早期終了(時間 / メモリ/ 確保回数) Slice 63.1ms / 8.0MB / 1 alloc 6.78ms / 8.0MB / 1 alloc Iter 64.7ms / 41.7MB / 41 allocs 3.43ms / 0B / 0 allocs 指標 © DMM.com -
パフォーマンスはどう変わったか(実測) 共通条件:1M items / Go 1.23.1 / M4 Pro 全件処理(時間
/ メモリ/ 確保回数) 早期終了(時間 / メモリ/ 確保回数) Slice 63.1ms / 8.0MB / 1 alloc 6.78ms / 8.0MB / 1 alloc Iter 64.7ms / 41.7MB / 41 allocs 3.43ms / 0B / 0 allocs 指標 Iterはわずかに不 利 © DMM.com -
パフォーマンスはどう変わったか(実測) 共通条件:1M items / Go 1.23.1 / M4 Pro 全件処理(時間
/ メモリ/ 確保回数) 早期終了(時間 / メモリ/ 確保回数) Slice 63.1ms / 8.0MB / 1 alloc 6.78ms / 8.0MB / 1 alloc Iter 64.7ms / 41.7MB / 41 allocs 3.43ms / 0B / 0 allocs 指標 Iterはわずかに不 利 © DMM.com Iter が約2倍高速 -
パフォーマンスはどう変わったか(実測) 共通条件:1M items / Go 1.23.1 / M4 Pro 指標
Slice Iter 全件処理(時間 / メモリ/ 確保回数) 早期終了(時間 / メモリ/ 確保回数) 63.1ms / 8.0MB / 1 alloc 6.78ms / 8.0MB / 1 alloc 64.7ms / 41.7MB / 41 allocs 3.43ms / 0B / 0 allocs 早期終了できるようなケースでは 圧倒的に高パフォーマンス Iterはわずかに不 利 © DMM.com Iter が約2倍高速 -
iterator化されなかったAPIもある os.ReadDir / filepath.Glob すべての APIがiterator向き とは限らない © DMM.com なぜ?
• APIはソート済みの結果を返す仕様。 • 全件を先に集める必要がある。 • 早期終了とメモリ節約が効かない。 -
xiterの提案は、どうなった? 2023.08 2024.07 xiter 提案 ・Map ・Concat ・Equal …etc Go
1.23対応 仕様に合わせ てxiter更新 © DMM.com -
xiterの提案は、どうなった? 2023.08 2024.07 Go 1.23 xiter 提案 ・Map ・Concat ・Equal
…etc Go 1.23対応 仕様に合わせ てxiter更新 iter、標準ライ ブラリへ © DMM.com -
xiterの提案は、どうなった? 2023.08 2024.07 Go 1.23 xiter 提案 ・Map ・Concat ・Equal
…etc Go 1.23対応 仕様に合わせ てxiter更新 iter、標準ライ ブラリへ © DMM.com 現在 not planned (お見送り) -
xiterの提案は、どうなった? 2023.08 2024.07 Go 1.23 xiter 提案 ・Map ・Concat ・Equal
…etc Go 1.23対応 仕様に合わせ てxiter更新 iter、標準ライ ブラリへ 現在 not planned (お見送り) 見送り理由 →標準に入れるほど共通の使い道がまだ見えていない © DMM.com -
sqlc: iterator API は今も議論中 Seq + Err() ループ内の err は不要
© DMM.com Seq2[T, error] -
sqlc: iterator API は今も議論中 Seq + Err() ループ内の err は不要
ループ内の err は不要 © DMM.com Seq2[T, error] -
sqlc: iterator API は今も議論中 Seq + Err() ループ内の err は不要
ループ内の err は不要 © DMM.com Seq2[T, error] -
sqlc: iterator API は今も議論中 Seq + Err() Seq2[T, error] ループ内の
err は不要 ループ内の err は不要 iteration中のエラーは返せる ただし、break後のClose()エラーをどのように利 用側に返すかは別問題 © DMM.com -
sqlc: iterの議論は終わらなかった 2024.10 #3631 2025.09 #4108 iterator API Seq+Err vs
Seq2 APIを決めず helperに限定 © DMM.com -
sqlc: iterの議論は終わらなかった 2024.10 #3631 2025.09 #4108 2026.06 #4488 iterator API
Seq+Err vs Seq2 APIを決めず helperに限定 Seq2を試す opt-in PoC © DMM.com -
sqlc: iterの議論は終わらなかった 2024.10 #3631 2025.09 #4108 2026.06 #4488 iterator API
Seq+Err vs Seq2 APIを決めず helperに限定 Seq2を試す opt-in PoC →sqlcのiterator APIはまだ決定版に至っていない。 range over funcで反復は共通化できても、共通の protocolだけでは、ドメイ ン固有のライフサイクル設計までは解決できない。 © DMM.com -
実際に動いているOSS実装 go-simpler.org/queries // go-simpler.org/queries func Query[T any]( ctx context.Context, q
Querier, query string, args ...any, ) iter.Seq2[T, error] for row, err := range queries.Query[Order](ctx, db, sql) { if err != nil { return err } } Close や defer を利用側が意識しなくていい設計に! © DMM.com -
比較 go-simpler sqlc ライブラリが責任を引き受ける 生成APIとして契約を決める必要がある Closeは利用者から隠す Closeやbreak時の扱いも設計対象 go-simpler:Closeとエラー処理をライブラリ 側で引き受ける設計を選んだ。 エラーは基本的に致命的。そこで停止
エラーの扱い方自体を検討中 sqlc:生成APIとしてどこまで利用者に委ね 狭い用途に対して意見を決めやすい 多くの利用者・既存コードへの影響が大きい るかを決める必要がある。 © DMM.com -
[]T を返すか、iter.Seq を返すか 複数回イテレーションする → []T 全件が必要(ソート・集計など) → []T 1回限りのストリーム
→ iter.Seq 早期終了の効果が大きい → iter.Seq 置き換え関係ではない。呼び出し側の利用特性で選ぶ。 © DMM.com -
Thank you! • • • • • Discussion #56413 github.com/golang/go/discussions/56413
Issue #61405 github.com/golang/go/issues/61405 Issue #61898 github.com/golang/go/issues/61898 sqlc PR #3631 github.com/sqlc-dev/sqlc/pull/3631 Go Wiki go.dev/wiki/RangefuncExperiment ご清聴ありがとうございました © DMM.com