The behavior of RangeFrom
is inconsistent when near the upper edge of representable types. This is documented as unspecified, but I think the current behavior is suboptimal due to its inconsistency.
Note : Currently, no overflow checking is done for the
Iterator
implementation; if you use an integer range and the integer overflows, it might panic in debug mode or create an endless loop in release mode. This overflow behavior might change in the future.
Specifically, of the implemented Iterator
methods, currently:
Iterator::size_hint
/TrustedLen
promise an endless iterator.Iterator::next
differs depending on debug/release:- In release mode, it wraps (
253u8, 254, 255, 0, 1, ...
). - In debug mode, it panics if it would return
Idx::MAX
(253u8, 254, panic, panic, ...
).
- In release mode, it wraps (
Iterator::nth
differs depending on debug/release:- In release mode, it wraps, but also panics if
Idx::MAX
would be skipped over.
- In release mode, it wraps, but also panics if
The "perfect world" behavior would be to consistently yield all values and then panic on further steps (253u8, 254, 255, panic, panic, ...
). Unfortunately, this is strictly impossible, as the contents of RangeFrom
are fully public and stable, and this behavior would require another bit of state.
pub struct RangeFrom<Idx> {
pub start: Idx,
}
So, how might we actually improve this situation? What behavior do we want out of RangeFrom
at the upper edge of representable types? My ideal priority list is
- Consistent behavior between
RangeFrom::next
andRangeFrom::nth
. There should be no difference between callingrange.nth(n)
andfor _ in 0..n { range.next() } range.next()
. - Consistency of debug/release split. Debug-checked overflow is consistent with the rest of the language, but consistent behavior from the iterator is also great (i.e. it'd be great if
(0u8..).take(256)
would work as expected both in debug and release). - No undue burden on custom types to be used in ranges (i.e. the
Step
trait). IdeallyStep
is implementable with just successor and predecessor functions.
The third is a loose and easily droppable goal, especially because Step
is unstable and probably far from stabilization.
The possible behaviors that meet the first goal of next
/nth
consistency I see are:
next
is right. Makenth
always wrap in release mode, rather than panicking when jumping overIdx::MAX
(consistent debug-checked overflow). This is also the smallest change to behavior, as it only makes a release mode panic ofnth
not panic.- Always wrap, even in debug mode, changing the behavior of both
next
(in debug mode) andnth
(in both modes). - Always panic like
next
does in debug mode. This changes release mode behavior ofnext
andnth
to deterministically panic when yieldingIdx::MAX
rather than setting up for a wrap. - Consistent saturating behavior (
235u8, 254, 255, 255, ...
). This is the most invasive change as it impactsnext
andnth
in both debug and release (but tbf only in cases where debug mode panics).
So what behavior do you think is the most correct? Do you see some potential behavior I overlooked?
(I'm currently leaning towards embracing debug-checked overflow and having nth
do wrapping in release mode. Whatever direction is chosen impacts the shape of the Step
rewrite I'm currently working on, as it has to support the behavior.)