Hi language experts,
Another little question about the implementation of ranges in Rust, specifically the newly stabilized inclusive range syntax 0..=255
(from RFC-1192).
Rust already has intervals with no explicit upper limit. They take the form 0..
and is represented by RangeFrom<T>
, see range expressions. Given that, I had expected 0..
to be the same as the new 0..=255
when iterating over u8
. However, it turns out that the iterator for RangeFrom
doesn't check for overflows! So this program crashes in debug builds (and loops endlessly in release builds):
let range_from: std::ops::RangeFrom<u8> = 0..;
println!("range_from: {:?}", range_from.last());
The equivalent snippet with RangeInclusive
works okay (see the playground):
let range_inclusive: std::ops::RangeInclusive<u8> = 0..=255;
println!("range_inclusive: {:?}", range_inclusive.last());
Now, luckily the documentation for RangeFrom
says:
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.
Does this mean that it would be possible to change the implementation of the integer iterators to simply count up to a value x
where x <= T::max_value()
for an integer type T
?
With such an implementation, the only reason to have the ..=
syntax seems to be for cases where the upper bound is a variable. When the upper bound is known, the programmer can simply write 100..200
or 200..
depending on how high the upper bound is.
Yes, the programmer will still need to be concious of the overflow and cannot write 0..256
, but that is something the compiler will detect at compile time. The advantage would be to make a very reasonable loop like for i in 0.. { println!("i: {}", i) }
work when i
is an u8
.
Thoughts?
I see that next
is implemented for RangeFrom
as follows:
fn next(&mut self) -> Option<A> {
let mut n = self.start.add_one();
mem::swap(&mut n, &mut self.start);
Some(n)
}
I guess my proposal of doing if self.start < T::max_value() { ... }
would mean more branches and would require an explicit flag -- so the iterator would use more memory. I looked very briefly at the evolution of that chunk of code, but didn't see any explicit argument for why it looks the way it does today.