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
Iteratorimplementation; 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/TrustedLenpromise an endless iterator.Iterator::nextdiffers 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::nthdiffers depending on debug/release:- In release mode, it wraps, but also panics if
Idx::MAXwould 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::nextandRangeFrom::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
Steptrait). IdeallyStepis 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:
nextis right. Makenthalways 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 ofnthnot panic.- Always wrap, even in debug mode, changing the behavior of both
next(in debug mode) andnth(in both modes). - Always panic like
nextdoes in debug mode. This changes release mode behavior ofnextandnthto deterministically panic when yieldingIdx::MAXrather than setting up for a wrap. - Consistent saturating behavior (
235u8, 254, 255, 255, ...). This is the most invasive change as it impactsnextandnthin 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.)