Bound checking really present?

Dear Rusteans,

I like to compare C++ and Rust because Rust gives new hopes for a safer programming experience.

Normally rustc is doing bound checking even for vectors ?
Because I tried this piece of code and it didn't catch the fact that I was trying to get an index outside of the bounds (well it did but at runtime and not at compile time), which is not correct :

fn main(){

    let mut vector : Vec<i32> = vec![1,2,3,4];
    
    for i in &vector{
        println!("{}", i);
    }

    println!("{}", &vector[10]);
}

It is correct that the bounds check is happening at run time. Since a vector can have any arbitrary size, there is no way to check the bounds at compile time.

2 Likes

No. Compile time bound checking is impossible in this case, because the size of the vector is variable. No, compilers aren't smart enough to know that the vector will ONLY have a size of 4. If you'd like it to be bounds checked, you should use something like an array, created at compile time and immutable.

Another way to look at it is that Vec in Rust is not a built-in language construct. It's a library feature (anyone can write their own vec), so the compiler doesn't understand what it is doing.

vector[10] desugars to a function call <Vec as std::ops::Index>::index(&vector, 10), which in this case panics, but it could as well play a song and return -1 on out of bounds access.

However, the safety — at run time — is still guaranteed.

4 Likes

Right, you need this to help the compiler:

If you don't want the default behavior (panic), you can use get, which returns an Option, so you can handle the error yourself.

Note that even a panic that you'd get by default is still better than C++, where it's undefined behavior and your program probably either segfaults or performs an out-of-bounds read.

On my personal wishlist is some way of trying to coerce a slice back into an array of known size (plus or minus other bits) so that I can fit it back onto the stack and I can have (optional) compile-time checks about out-of-bounds accesses on slices if I want, among other things. Such a feature might have helped in this case.

A not-too-much-nice version of that is available in Nightly:

#![feature(try_from)]

use std::convert::{TryFrom, TryInto};

fn foo(a: &[u8; 4]) {
    println!("{:?}", a);
}

fn main() {
    let data = [10, 20, 30, 40, 50];

    let conv1 = <&[u8; 4]>::try_from(&data[1..]);
    foo(conv1.unwrap());

    foo(<_>::try_from(&data[1..]).unwrap());

    let conv2: Result<&[u8; 4], _> = data[1..].try_into();
    foo(conv2.unwrap());

    foo(data[1..].try_into().unwrap());
}
1 Like

I'm not sure that's quite true.

You're right that Vec is a standard library type, which means that its semantics aren't really governed by the core language. However:

  • The core language also doesn't guarantee the correct use of unsafe blocks. It's the standard library that promises to use unsafe constructs to produce safe abstractions. The language just provides the tools to do so.
  • The standard library also guarantees, in the same breath, that out of bounds access on a vector shall panic. It will not play a song or summon a return value from nowhere.