Upgrade to Pro — share decks privately, control downloads, hide ads and more …

range over func 2年間の軌跡 Issue #56413 はGoのエコシステムを...

Avatar for Ryuji Kokubu Ryuji Kokubu
September 11, 2026

range over func 2年間の軌跡 Issue #56413 はGoのエコシステムをどう変えたか

Go Conference2026にて登壇した内容になります

Avatar for Ryuji Kokubu

Ryuji Kokubu

September 11, 2026

More Decks by Ryuji Kokubu

Other Decks in Technology

Transcript

  1. 改めて復習 • 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
  2. この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. この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 どれもとあるコレクションを走 査するメソッドです
  4. この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 どれもとあるコレクションを走 査するメソッドです
  5. 同じ (T, bool) でも意味が違う // 多くの慣習: bool は「Tが有効かどうか」 v, ok

    := m[key] // okがtrueならvは有効 // runtime.Frames.Next は違う frame, more := frames.Next() // more は「次の呼び出しが有効な値を返すか」 © DMM.com
  6. 同じ (T, bool) でも意味が違う // 多くの慣習: bool は「Tが有効かどうか」 v, ok

    := m[key] // okがtrueならvは有効 // runtime.Frames.Next は違う frame, more := frames.Next() // more は「次の呼び出しが有効な値を返すか」 同じ形をしていても、中身の意味は関数ごとに学び直す必要があった。 © DMM.com
  7. 2022年10月25日、議論が動き出す Discussion #56413 なぜ動き出したか “user-defined iteration using range over func

    values” Goにはシーケンシャルなデータを反復処理する標 © DMM.com 準的な方法がなかった。各実装が個別最適化し た結果、Goユーザーのキャッチアップコストが増 大していた。
  8. 実験から正式リリースまでの時系列 2022.10 Discussion #56413 オープン © DMM.com → 2023.07 Issue

    #61405 提案提出 → Go 1.22 GOEXPERIMENT=rangefunc で試験導入 → Go 1.23 2024.08 正式リリース 言語機能として有効化
  9. 設計の決断 みなさんなら、 interfaceで解決しませんか? type Iterator[T any] interface { Next() (T,

    bool) } // でもGoチームはこの道を選ばなかった 答えは、言語仕様そのものへの組み込みでした。 © DMM.com
  10. Issue #61405 — rsc、2023年7月17日 “spec: add range over int, range

    over func” Discussion #56413 の議論を受けて、 for-range で整数と関数を扱えるようにする 言語拡張として提案が出された © DMM.com 11
  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
  12. 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
  13. Push と Pull:誰が次の1件を主導するか Pull Iterator Push Iterator Push / Pullは「値の向き」ではなく

    yieldを呼び出し、値を iterationの主導権がどちらにあるかを表します mainにpush main側が明示的に 次の値を呼び出す next関数を実行 © DMM.com
  14. 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
  15. 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
  16. rangeの中のdeferは、いつ実行される? for v := range seq { defer cleanup(v) …

    } 1回ごとにdefer? 答え:外側の関数が returnするとき。他の rangeループと同じ © DMM.com
  17. 固定回数のループ・逆順走査 BEFORE for i := 0; i < n; i++

    { /* ... */ } for i := n-1; i >= 0; i-- { _ = s[i] } © DMM.com
  18. 固定回数のループ・逆順走査 BEFORE for i := 0; i < n; i++

    { /* ... */ } ①よくあるforループ for i := n-1; i >= 0; i-- { _ = s[i] } © DMM.com
  19. 固定回数のループ・逆順走査 BEFORE for i := 0; i < n; i++

    { /* ... */ } ①よくあるforループ for i := n-1; i >= 0; i-- { _ = s[i] } © DMM.com ②逆順にした途端 あれ、条件は? イテレーションの向きは?
  20. 固定回数のループ・逆順走査 BEFORE for i := 0; i < n; i++

    { /* ... */ } ①よくあるforループ パニック for i := n-1; i >= 0; i-- { _ = s[i] } © DMM.com ②逆順にした途端 あれ、条件は? イテレーションの向きは?
  21. 固定回数のループ・逆順走査 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) { // ... }
  22. 固定回数のループ・逆順走査 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) { // ... }
  23. 固定回数のループ・逆順走査 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) { // ... }
  24. maps.Keys / Values は、なぜ iterator になった? 2021.07 #47330 2022.12 #57436

    maps 構想 Keys / Values →sliceを返す Go 1.21向けに x/exp/maps 標準化を提案 © DMM.com
  25. maps.Keys / Values は、なぜ iterator になった? iter構想 2021.07 #47330 2022.12

    #57436 maps 構想 Keys / Values →sliceを返す Go 1.21向けに x/exp/maps 標準化を提案 © DMM.com
  26. 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
  27. 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
  28. 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
  29. パフォーマンスはどう変わったか(実測) 共通条件: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
  30. パフォーマンスはどう変わったか(実測) 共通条件: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
  31. パフォーマンスはどう変わったか(実測) 共通条件: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倍高速
  32. パフォーマンスはどう変わったか(実測) 共通条件: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倍高速
  33. iterator化されなかったAPIもある os.ReadDir / filepath.Glob すべての APIがiterator向き とは限らない © DMM.com なぜ?

    • APIはソート済みの結果を返す仕様。 • 全件を先に集める必要がある。 • 早期終了とメモリ節約が効かない。
  34. xiterの提案は、どうなった? 2023.08 2024.07 Go 1.23 xiter 提案 ・Map ・Concat ・Equal

    …etc Go 1.23対応 仕様に合わせ てxiter更新 iter、標準ライ ブラリへ © DMM.com
  35. xiterの提案は、どうなった? 2023.08 2024.07 Go 1.23 xiter 提案 ・Map ・Concat ・Equal

    …etc Go 1.23対応 仕様に合わせ てxiter更新 iter、標準ライ ブラリへ © DMM.com 現在 not planned (お見送り)
  36. xiterの提案は、どうなった? 2023.08 2024.07 Go 1.23 xiter 提案 ・Map ・Concat ・Equal

    …etc Go 1.23対応 仕様に合わせ てxiter更新 iter、標準ライ ブラリへ 現在 not planned (お見送り) 見送り理由 →標準に入れるほど共通の使い道がまだ見えていない © DMM.com
  37. sqlc: iterator API は今も議論中 Seq + Err() ループ内の err は不要

    ループ内の err は不要 © DMM.com Seq2[T, error]
  38. sqlc: iterator API は今も議論中 Seq + Err() ループ内の err は不要

    ループ内の err は不要 © DMM.com Seq2[T, error]
  39. sqlc: iterator API は今も議論中 Seq + Err() Seq2[T, error] ループ内の

    err は不要 ループ内の err は不要 iteration中のエラーは返せる ただし、break後のClose()エラーをどのように利 用側に返すかは別問題 © DMM.com
  40. 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
  41. 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
  42. 実際に動いている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
  43. 比較 go-simpler sqlc ライブラリが責任を引き受ける 生成APIとして契約を決める必要がある Closeは利用者から隠す Closeやbreak時の扱いも設計対象 go-simpler:Closeとエラー処理をライブラリ 側で引き受ける設計を選んだ。 エラーは基本的に致命的。そこで停止

    エラーの扱い方自体を検討中 sqlc:生成APIとしてどこまで利用者に委ね 狭い用途に対して意見を決めやすい 多くの利用者・既存コードへの影響が大きい るかを決める必要がある。 © DMM.com
  44. []T を返すか、iter.Seq を返すか 複数回イテレーションする → []T 全件が必要(ソート・集計など) → []T 1回限りのストリーム

    → iter.Seq 早期終了の効果が大きい → iter.Seq 置き換え関係ではない。呼び出し側の利用特性で選ぶ。 © DMM.com
  45. 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