Read from vector out of bounds (kind of)

As a short summary, I'm accessing parts of a vector by slicing it.
The example below lists a stripped down version of the part of the code that got me confused.
Why does the second to last line not panic at all? The vector is of length 2 and I'm going to slice it by starting with the index 2 which should be out of bounds?!
If the second to last line is valid, why does the last line after all behaves differently by panicking?

fn main() {
    let foo = vec!["ABC", "DEF"];
    
    println!("{:?}", &foo[0..]); // ["ABC", "DEF"]
    println!("{:?}", &foo[1..]); // ["DEF"]
    println!("{:?}", &foo[2..]); // [] -> Wtf?!
    println!("{:?}", &foo[3..]); // panic: range start index 3 out of range for slice of length 2
}

Link to Playground

This is just consistency. For a vector with 2 elements, half-inclusive ranges with an upper bound of 2 are valid. This set includes the empty range [2, 2).

3 Likes

On a lower level, this is also a reflection of how pointer provenence generally works: You're allowed to point at the address that immediately follows you. Otherwise you couldn't do things like iterating a slice by adding to a pointer until it's equal to the address immediately following the slice.

&foo[2..] is the slice of (zero) elements immediately following the first 2 elements.

    let all = &foo[0..];
    let (ptr, len) = (all as *const _ as *const &str, all.len());
    println!("All: {ptr:p} {len}");

    let tail = &foo[2..];
    let (tptr, tlen) = (tail as *const _ as *const &str, tail.len());
    println!("2..: {tptr:p} {tlen}");
    
    assert_eq!(tlen, 0);
    assert_eq!(ptr.wrapping_add(2), tptr);

You can't (safely) read any out of bounds memory from it, because you can't (safely) read any memory from it: it points to a span of memory covering 0 bytes.

5 Likes