Unsafe Array Indexing

Hi all,
I'm creating a simple game of tic-tac-toe and would like help understanding arrays.
I was under the assumption that Rust would not compile if your code is capable of panicking.
However, in the below code, I index a 2D array in try_update_cell() in an unsafe manner and I am capable of compiling this with zero errors or warnings, in release mode.
Could someone kindly clarify if this is intentional behaviour, and why?
Am I operating on a false assumption?

src/core/board.rs
use std::fmt;
use super::cell_value::CellValue;

#[derive(Debug, Default)]
pub struct Board {
    cells: [[CellValue; 3]; 3]
}

impl Board {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn try_update_cell(&mut self, row: usize, column: usize, value: CellValue) {
        self.cells[row][column] = value
    }
}

impl fmt::Display for Board {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f,
               "\
โ•ญโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ•ฎ
โ”‚ {} โ”‚ {} โ”‚ {} โ”‚
โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค
โ”‚ {} โ”‚ {} โ”‚ {} โ”‚
โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ค
โ”‚ {} โ”‚ {} โ”‚ {} โ”‚
โ•ฐโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ•ฏ",
               self.cells[0][0],
               self.cells[0][1],
               self.cells[0][2],
               self.cells[1][0],
               self.cells[1][1],
               self.cells[1][2],
               self.cells[2][0],
               self.cells[2][1],
               self.cells[2][1],
        )
    }
}

#[cfg(test)]
mod tests {
    use crate::core::board::Board;

    fn display() {
        let board = Board::new();
        println!("{}", board)
    }
}

No, this is false. (It wouldn't even made sense to have panics as a concept if all of them were detected at compile time.)


Unrelated, but there isn't anything unsafe in your code.

What I meant by "unsafe" is that the code panics if I index either row or column out-of-bounds (5, for example).

A bit disappointed Rust doesn't make guarantee of that especially given its focus on correctness, but oh well.

That's not what unsafe means in rust. (And panics aren't unsafe.)

This is not a guarantee that is possible to provide.

If you want to use the type system better, use iterators or .get() instead of indexing.

The safety Rust provides (around array access), and that you might have heard about hence are wondering, are those panics. It panics because it is checking for out-of-bounds access, whereas less safe languages donโ€™t, creating genuinely unsafe situations (which might crashโ€ฆ or notโ€ฆ).

Proving out of bounds access statically is not generally possible. At runtime, arrays (Vec) may grow and shrink dynamically. No language can foresee the future like that.

Rust also has arrays (slices) of statically known length though, where static checks for out of bound access are possible in limited fashion.

4 Likes

Adding some theoretical background to that: In fact it can be shown to be impossible by trivial equivalence with the halting problem: you could make the length of a vector depend on the output of an arbitrary computation. Or you make your program access an index based on arbitrary computation.

And then separately there is of course also IO: Why not just ask the user to enter the index on standard input?

1 Like

Do not be disappointed. Be happy your program panics when your code runs off the end of an array. Rather than continuing with some erroneous result or crashing mysteriously: Which can waste a lot of time in debugging. Which can cause you to get calls in the middle of the night to fix some rarely occurring problem that just happened to occur.

As noted above, it is not even possible for the compiler to know what weird situations your program will run into when run. So the safety checks necessarily have to be inserted into the code to cause panics at run time.

Also as noted above one can use iterators and such to avoid array indexing in the first place. Which means the compiler builds the code for you and run-time checks can be avoided.

1 Like