Array Boundary not checked in a const genreric wrapper

I wrote a wrapper for an array using const generic like this:

struct Pool<const SIZE: usize> {
    data: [u8; SIZE],
}

impl<const SIZE: usize> Pool<SIZE> {
    fn new() -> Self {
        Pool {
            data: [0; SIZE],
        }
    }
    fn get<const INDEX: usize>(&self) -> &u8 {
        &self.data[INDEX]
    }
}

fn main() {
    let pool = Pool::<3>::new();
    println!("{:?}", pool.get::<3>());
}

In this case, pool.get::<3>() apparently read an element which out of the boundary, but this code compiles. The boundary check is done at runtime, instead of compile time. Did I do anything wrong?

P.S. A simpler example:

fn foo<const INDEX: usize, const SIZE: usize>(array: &[u8; SIZE]) {
    let _i = array[INDEX];
}

fn main() {
    let array = [0u8; 3];
    foo::<5, 3>(&array);
}

Was there a question somewhere?

As it should.

Depends on what you wanted to achieve.

found something may related: Why index out of bound is not checked at Compile Time

The case discussed there is not exactly the same with what I met, though.

For better or worse, array indexing happens at runtime, and the bounds checking is not a separate part of the indexing operation. However, I don’t think there’s a fundamental reason why the compiler couldn’t be helpful and lint known out-of-bounds accesses, except that if it were a strict error it would make existing valid code stop compiling (probably tests mostly). Even a warning could be annoying, and break builds that have warnings set to deny. A Clippy lint might make sense, at least as a first step.

It's possible to add a compile-time checked bound maunally, but this requires nightly and an unstable feature:

#![feature(generic_const_exprs)]

trait True {}

struct Bool<const TERM: bool>();

impl True for Bool<true> {}

fn foo<const INDEX: usize, const SIZE: usize>(array: &[u8; SIZE])
where
    Bool<{ INDEX < SIZE }>: True,
{
    let _i = array[INDEX];
}

fn main() {
    let array = [0u8; 3];
    foo::<5, 3>(&array);
}

In the future we might get a nicer syntax like where INDEX < SIZE.

Actually it requires beta, not nightly if done like this:

    fn get<const INDEX: usize>(&self) -> &u8 {
        const { if INDEX >= SIZE {
            panic!("Index too big");
        } };
        &self.data[INDEX]
    }

In one month this should work on stable.

5 Likes

Nice

This can also be written like this in stable:

    fn get<const INDEX: usize>(&self) -> &u8 {
        struct Check<const I: usize, const S: usize>;
        impl<const I: usize, const S: usize> Check<I, S> {
            const CHECK: () = assert!(I < S);
        }
        _ = Check::<INDEX, SIZE>::CHECK;
        &self.data[INDEX]
    }

However note that both solutions won't report errors when you run cargo check.

4 Likes