Open ranges semantics

This is probably a dumb question, but sometimes that's what I do. What's the expected output of this code?

fn main() {
    for i in 250_u8 .. {
        println!("{}", i);
    }
}

Naively I expected it to scan and print the last values of an u8.
Thinking a second more about the semantics of .., I expect it to not print the last u8.

(So what can I do if I want the last u8 too? 250_u8..= is not allowed for some reason. So I need the whole 250 ..= u8::MAX for that, OK, and I am grateful of that because in past doing this was messier).

So I expect it to print up to the penultimate u8 value and then stop.
With a debug compilation I get a panic:

250
251
252
253
254
thread 'main' panicked at 'attempt to add with overflow', ...\src\libcore\ops\arith.rs:9

In an optimized build I get an infinite loop:

250
251
252
253
254
255
0
1
2
3
4
5
6
...

Is all this good?

If I have an array with all the u8s I can take and print the last items:

let data: [u8; 256] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
    14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
    30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
    46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
    62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
    78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
    94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
    108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120,
    121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
    134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
    147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
    160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172,
    173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185,
    186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198,
    199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,
    212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
    225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237,
    238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250,
    251, 252, 253, 254, 255];
for x in &data[250 ..] {
    println!("{}", x);
}

That's the (wrong) mental model I have used in the past for right-open intervals -.-

1 Like

One mental model is that a right-open range doesn't enforce an end limit, but something else might. In the &data[250..] case, the range doesn't have a limit, but the data itself has a limit, so that gets used. In the for i in 250_u8.. case, the maximum value of u8 is the source of the limit, and what happens at the limit is determined by the overflow behaviour.

The RangeFrom docs do point out (in bold!) that the overflow behaviour could be changed in a future Rust version, so don't depend on it:

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.

1 Like

Code like this contributed to put me off track:

fn foo() -> u32 {
    for i in 250_u8 .. {
        if false {
            return 0;
        }
    }
    // Error: expected `u32`, found `()`
}
fn main() {
    let _x = foo();
}

If the semantic of for i in 250_u8 .. {} is to cause a panic or an infinite cycling loop, then I kind of expect code like that to not require a return at the end (as it happens if I replace the for with a loop {}. I guess it's because the semantics of open ranges isn't yet set in stone).

AFAIK the reason is simpler: for loop doesn't distinguish between finite and infinite iterators. for i in std::iter::repeat(1) loop endlessly too, but this is not shown on type-level.

1 Like

Yes, that makes sense. The closest that Rust gets to automatic infinite loop detection is for warnings in simple cases, eg this:

fn foo() -> u32 {
    while true {
        return 0;
    }
    // Error: expected `u32`, found `()`
}

fn main() {
    let _ = foo();
}

produces this warning:

warning: denote infinite loops with `loop { ... }`
 --> src/main.rs:2:5
  |
2 |     while true {
  |     ^^^^^^^^^^ help: use `loop`
  |

and it fails to compile too, just like your for loop example. It is hard to detect all types of infinite loop, so rather than have complex rules to catch a subset of infinite loops, in Rust there's a simple rule instead: only loop gets special treatment. But it is a trade-off, since while true and friends are common conventions in languages without an explicit loop.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.