RangeFromイテレータの予期せぬ挙動とは
RustのRangeFromイテレータ(n..)は、指定された値から無限に続く数値シーケンスを生成することを期待されます。しかし、2025年12月にJames Munns氏が指摘したように、このイテレータの挙動には設計上の課題があり、開発者の期待と異なるケースが存在します。具体的には、以下の4つの点で問題が指摘されています。
- 最大値のスキップ: オーバーフローチェックが有効な場合、
u8::MAXのような型の上限値がイテレートされずにパニックが発生します。 - レンジ外の値の生成: オーバーフローチェックが無効な場合、レンジの開始値(
n)をcontains()メソッドでチェックしても、オーバーフローによって生成される値がレンジ外となることがあります。 - オーバーフロー時のパニック: オーバーフローチェックがオフであっても、一部の型(
Ipv4Addrなど)ではパニックが発生します。 - 挙動の不一貫性: 異なる型間でイテレータの挙動が一貫していません。
「RangeFromイテレータは、開発者が直感的に期待する動作とは異なる挙動を示すケースがあるため、注意が必要です。」
特に2番目の「レンジ外の値の生成」は、筆者も実際に遭遇したらかなり戸惑うだろうと感じます。if !range.contains(&i) が unreachable! にならないというのは、レンジの定義そのものへの理解が揺らぎそうです。
1. 最大値のスキップとオーバーフローチェック
RangeFromイテレータは、内部カウンターを値の出力前にインクリメントする実装となっています。このため、オーバーフローチェックが有効な環境では、以下のコードが u8::MAX を出力する前にパニックします。
for i in 253u8.. {
println!("{i}");
}
このコードは253, 254を出力した後にパニックします。オーバーフローチェックが無効な場合は255も出力されますが、この挙動は新しいレンジ型で改善されています。std::range::RangeFrom::from(253u8..).into_iter() を使用した場合、オーバーフローチェックが有効なら253, 254, 255を出力後にパニックします。
しかし、無効な場合は無限ループに陥ります。(出典)
2. レンジ外の値がイテレートされる可能性
現行のRangeFromイテレータでは、オーバーフローが発生すると、レンジの範囲外の値が生成される可能性があります。これは、range.contains(&i) のチェックが unreachable! になることを期待する開発者の直感に反します。全ての整数型(u*およびi*)でこの問題が発生し、レンジを値のガードとして使用する際には、オーバーフローの考慮が必須となります。
例えば、n..={Integer}::MAX のように上限を明示的に指定することで、この問題を回避できます。(出典)
3. オーバーフローチェックオフでもパニックする型
Rustでは通常、オーバーフローチェックがオフの場合、整数型のオーバーフローはラップアラウンド(最大値を超えると最小値に戻る)挙動となります。しかし、Stepトレイトを実装する一部の型では、オーバーフローチェックがオフでもパニックが発生します。例えば、Ipv4AddrやIpv6Addrは、基盤となる整数型が完全にマッピングされているにもかかわらず、オーバーフロー時に常にパニックします。
// library/core/src/iter/range.rs:118:45: overflow in `Step::forward` で常にパニック
for i in Ipv4Addr::new(255, 255, 255, 250).. {
println!("{i}");
}
これはStepトレイトの実装ノートに起因するものですが、開発者からすると一貫性のない挙動に感じられるのではないでしょうか。
新しいRangeFrom型と従来の比較
今回の課題を受けて、新しいレンジ型が導入されています。従来のRangeFromと新しいレンジ型の主な違いは以下の通りです。
| 項目 | 従来のRangeFrom(n..) |
新しいレンジ型(std::range::RangeFrom::from(n..).into_iter()) |
|---|---|---|
u8::MAXの出力(OFC:ON) |
スキップされパニック | 出力されパニック |
u8::MAXの出力(OFC:OFF) |
出力される | 無限ループ |
| レンジ外の値の生成 | 発生しうる | 従来のRangeFromと同様 |
Ipv4AddrでのOFCオフ時のパニック |
発生する | 発生する |
新しいレンジ型でもオーバーフローチェックオフ時の無限ループなど、まだ課題は残るものの、u8::MAXのような最大値がスキップされる問題は改善されていることがわかります。
自分の個人プロダクトでも、RangeFromを使った無限ループ的な処理を組むことがあり、特にRustのような型安全性の高い言語でこのような「直感的でない」挙動があるのは少し驚きです。ドキュメントをしっかり読むことの重要性を改めて感じますね。