Why can I iter on 0.. but not ..=0?

It seems inconsistent that I can iterate upwards ( 0.. ) but I can't start from the minimum and head towards 0 ( ..=0 ).

note: `..=end` is a `RangeToInclusive`, which cannot be iterated on; you might have meant to have a bounded `RangeInclusive`: `0..=end`

The RangeFull ( .. ) does not have a type associated with it which I fear makes it impossible to ever iterate across. But for the others, are there good reasons why we should not try and make as many of the range types iterable?

0.. does not iterate from 0 to max, but iterates from 0 indefinitely, triggering overflow behavior if you reach the end of the type. For example, in debug mode, this panics (playground):

fn main() {
    for _ in 0u8.. {}
}

The consistent behavior for for _ in ..0 {} would be to panic immediately.

13 Likes

It overflows? That is disappointing. So it really does mean to infinity and beyond. I guess that is quite idiomatic in terms of purity...

This being rust, the actual problem I'm trying to solve is to figure out if a type T is signed but as MIN isn't on a trait this is proving tricky.

I suppose you're trying to avoid num-traits for this?

You must have a way to produce T's zero, I guess -- if you can also get a one then you could try something like -Wrapping(1) < Wrapping(0).

If you are dead set on not using num, here's an egregious hack you could use:

Require Default + Not + PartialOrd, and ask whether (!T::default()) < T::default().

4 Likes

Isn't ..=x comparable to (x..).rev()? Indeed, DoubleEndedIterator is not implemented for RangeFrom https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=054b93e137bab3064a9a30c21ea9020a. I would say the behavior is consistent here.

A mate suggested this which works quite nicely:

where T: std::convert::TryFrom<i8>

T::try_from(-1_i8).is_ok()
3 Likes

I'm not sure if this is what you were saying, but the idea of using rev is a fascinating thought. The only reason ..0 isn't an Iterator is that an Iterator is something that has a beginning but not necessarily an end, while ..0 has an end, but no beginning! Thinking of it that way, it wouldn't be unreasonable to create a method that would let you say (..=0).rev() to get an iterator that starts at zero and goes backwards into the negative numbers. (It couldn't use the trait method Iterator::rev, so it would probably just be an inherent method, since this doesn't seem like a common situation that you would need to abstract over. But still.)

It kind of seems like there should be some invariant that let y = x.rev().collect() produces the same thing as let mut y = x.collect(); y.reverse(); though. Admittedly less applicable for infinite sequences.

The only reason they wouldn't produce the same collection is that they [compile error/panic] before you can [start/finish] iterating through the infinite amount of negative numbers :joy:

(and since they never actually produce a collection in the first place, there's no contradiction in practice either)

It falls down for any collect that doesn’t keep all the values, too. [Err(1), Ok(2), Err(3)].into_iter().collect::<Result<_, _>>() == Err(1), but [Err(1), Ok(2), Err(3)].into_iter().rev().collect::<Result<_, _>>() == Err(3).

Or a HashMap with duplicate keys. But I can solve all these problems by revealing that type inference had y : InfiniVec<_> this whole time! Anyway I don't thing rev should change the content of an iterator, just the order.

1 Like

Are you saying that my suggestion changes the content of an iterator? It doesn't – not just for the trivial reason that one of the directions isn't an iterator at all, but because the contents of ..=0 are exactly the numbers [0, -1, -2...] that I I'm suggesting for (..=0).rev() to iterate over.

1 Like

That makes sense!