Every two-dimensional array is a one-dimensional array with index operations not just translating to a simple offset, but to a multiplication and addition operation to determine the actual offset. For a two-dimensional array with a total size of A * B, where A designates the size of the first dimension and B designates the size of the second dimension, accessing the element [x][y] is equal to accessing the element [x * B + y].
If you're really set on the 2D array approach perhaps try something like this: Rust Playground
It's a bit ugly, but you could probably wrap it in a nicer interface.
As an old time assembler programmer I am well aware of that. I really did not want to do that, its seem like a horrible band-aid in a high level language where we have a syntax for defining and indexing multiple dimensional arrays.
After tinkering around I found I can get what a want with a little sprinkling of 'unsafe':
type T = [[i64; WIDTH]; HEIGHT];
fn main() {
let mut a = unsafe {
let a = Box::<T>::new_uninit();
let mut a = a.assume_init();
for i in 0..a.len() {
for j in 0..a[i].len() {
a[i][j] = 0;
}
}
a
};
Allocating with zeroes as I suggested above might be safer in some cases. It also might not be. But zero is pretty much always at least a defined variant of an enum. It might not be logically correct to leave it zeroed but it will at least be an actual value your program can handle. Same with integers, characters, etc. Null pointers would be the biggest issue.
But that’s just if you for some reason didn’t follow up with properly initializing the memory after the zero init. Your strategy above has the same downside if not worse because the memory might have arbitrary bits in it.
let mut a: Box<[Box<[i64]>]> = vec![vec![0i64; WIDTH].into_boxed_slice(); HEIGHT].into_boxed_slice();
Is a Box of Boxes. Which is not a huge contiguous glob of memory like a C style 2D array. If I understand correctly. Normally I would not mind but I set my heart on it for this exercise.
Did you read my suggestion? I'm quite sure it's simpler to understand and extends better as the dimensionality increases. It uses unsafe, but is fine because it doesn't actually try to use the value till it's completely initialized.
Obviously you'll have to adapt some of the type names and such because I excerpted it directly from some of my own code:
unsafe {
let layout = std::alloc::Layout::new::<World>();
let ptr = std::alloc::alloc_zeroed(layout) as *mut World;
Box::from_raw(ptr)
}
Are you just averse to unsafe? I'm just curious because the iterator solution has mighty many parts to keep track of just to produce a 2D array of zeroes on the heap.
Not at all. I have been living in 'unsafe' world for decades in C/C++ and other languages. If anything I'm averse to all this unintelligible function style with it's iterators and maps and lambda functions etc.
float A[MAX, MAX], B[MAX, MAX];
for (i=0; i< MAX; i+=block_size) {
for (j=0; j< MAX; j+=block_size) {
for (ii=i; ii<i+block_size; ii++) {
for (jj=j; jj<j+block_size; jj++) {
A[ii,jj] = A[ii,jj] + B[jj, ii];
}
}
}
}
Well, I thought, how would those kool kids using iterators and such do this in Rust? Time to give it a try, just as a learning exercise. Hence the code in the playground above.
Great! I have somewhat of a PTSD from having tried to explain array-to-pointer decay in C to too many people on Stack Overflow, so I constantly have the "you know the size of every dimension except the outermost one" idea in the back of my mind.