Slice range inconsistency

Hi, Can anyone please explain why this works?

let numbers = &[1,2];
println!("{:?}",&numbers[2..]) // prints "[]"

and doesn't throw an index out of range error.

But this throws an error:

let numbers = &[1,2];
println!("{:?}",&numbers[2..3]) // thread 'main' panicked at 'range end index 3 out of range for slice of length 2'

Now, according to the docs for Range, a range is empty if start >= end, so it may mean that passing an empty range into a slice (compiler may replace [2..] with [2..len()] ?) returns an empty slice, but by that logic, this doesn't work.

let numbers = &[1,2];
println!("{:?}",&numbers[3..3]); // thread 'main' panicked at 'range end index 3 out of range for slice of length 2'

I'm passing in an empty range 3..3 and it panics, but passing in the empty range 2..2 works instead.

let numbers = &[1,2];
println!("{:?}",&numbers[2..2]); // prints "[]"

Is there some semantics I'm missing in the implementation of a slice?

This is not "inconsistency", this is consistency. The .. syntax creates a half-open range, i.e. one that is inclusive on the left and exclusive on the right, therefore a..b means the set of indices a, a+1, …, b-2, b-1. Therefore, allowing the slice to be indexed with a range where end = slice.len() is perfectly valid, because the last index is thus slice.len() - 1, which is indeed the last valid index of the slice. Higher indices, however, are not allowed, because they are out of bounds.

1 Like

Thanks a lot for the explanation, although, if a..b means at least a, then in the example above, the range must include 2, and 2 is greater than slice.len() - 1, why doesn't that throw an index out of range error?

But it doesn't, because it's empty.

The point is: allowing len()..len() to return an empty slice leads to more consistency and more uniform code, because you can create subslices with lengths len() down to 0 without needing to special case the 0-length case. So in this case indexing with a range that syntactically/conceptually refers to the length (but doesn't actually include it!) is legitimate. However, indexing with a range that refers to an index greater than the length is never correct.

2 Likes

Thanks for the explanation, it makes sense now.

I know the topic is solved, but wanted to also give a visual explanation:

offset:  0   1   2   3
         |   |   |   |
numbers: [ 1 , 2 ]
0..2:    [-------]
1..2:        [---]
2..2:            |
3..3                 |

Btw, I've recently needed to use empty ranges for Vec::splice – they're very helpful if you only care about "inserting" aspect of this splice method.

5 Likes