Well, you can do this:
let grid = (0..128).map(|_| {
(0..128).map(|_| {
(0..128).map(|_| {
vec![0i32; 128]
}).collect::<Vec<_>>().into_boxed_slice()
}).collect::<Vec<_>>().into_boxed_slice()
}).collect::<Vec<_>>().into_boxed_slice();
But that still has 128*128 allocations and takes forever. If you want to be clever, you may be able to (@nikomatsakis?) abuse slices and do this (it only works for squares, cubes, etc...):
use std::ops::{Deref, DerefMut, Index, IndexMut};
use std::mem::transmute;
use std::slice::{from_raw_parts, from_raw_parts_mut};
struct Grid<T> {
size: usize,
data: Box<[T]>,
}
impl<T> Grid<T> where T: Clone {
pub fn new_from(v: T, size: usize) -> Self {
Grid {
data: vec![v; size*size*size].into_boxed_slice(),
size: size,
}
}
}
impl<T> Deref for Grid<T> {
type Target = Cube<T>;
fn deref(&self) -> &Cube<T> {
unsafe { transmute(&self.data[..self.size]) }
}
}
impl<T> DerefMut for Grid<T> {
fn deref_mut(&mut self) -> &mut Cube<T> {
unsafe { transmute(&mut self.data[..self.size]) }
}
}
pub struct Cube<T>([T]);
impl<T> Index<usize> for Cube<T> {
type Output = Square<T>;
fn index(&self, idx: usize) -> &Self::Output {
unsafe {
let slice: &[T] = transmute(self);
let len = slice.len();
if idx >= len {
panic!("index out of bounds: the len is {} but the index is {}", len, idx);
}
let offset = len*len*idx;
let ptr = slice.as_ptr().offset(offset as isize);
transmute(from_raw_parts(ptr, len))
}
}
}
impl<T> IndexMut<usize> for Cube<T> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
unsafe {
// Me being careful about anti-aliasing rules.
let (ptr, len) = {
let slice: &mut [T] = transmute(self);
let len = slice.len();
if idx >= len {
panic!("index out of bounds: the len is {} but the index is {}", len, idx);
}
let offset = len*len*idx;
let ptr = slice.as_mut_ptr().offset(offset as isize);
(ptr, len)
};
transmute(from_raw_parts_mut(ptr, len))
}
}
}
pub struct Square<T>([T]);
impl<T> Index<usize> for Square<T> {
type Output = [T];
fn index(&self, idx: usize) -> &Self::Output {
unsafe {
let slice: &[T] = transmute(self);
let len = slice.len();
if idx >= len {
panic!("index out of bounds: the len is {} but the index is {}", len, idx);
}
let offset = len*idx;
let ptr = slice.as_ptr().offset(offset as isize);
from_raw_parts(ptr, slice.len())
}
}
}
impl<T> IndexMut<usize> for Square<T> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
unsafe {
// Me being careful about anti-aliasing rules.
let (ptr, len) = {
let slice: &mut [T] = transmute(self);
let len = slice.len();
if idx >= len {
panic!("index out of bounds: the len is {} but the index is {}", len, idx);
}
let offset = len*idx;
let ptr = slice.as_mut_ptr().offset(offset as isize);
(ptr, len)
};
transmute(from_raw_parts_mut(ptr, len))
}
}
}
fn main() {
use std::cell::Cell;
let g = Grid::new_from(Cell::new(0i32), 128);
g[0][0][0].set(10);
println!("{}", g[0][0][0].get());
println!("{}", g[127][127][127].get());
}
This will allocate everything all at once and put it in one location. Basically, I'm encoding the size of the cube/square in the length of the slice so the slices are actually one-dimensional prefixes of the real slices.
If you know the size of the grid at compile time (or are willing to spend time calculating cube/square roots), you can just use normal slices and avoid this hack (although you'll still probably want to transmute your slices into custom DSTs).