use super::array::try_make_array;
use core::{
convert::Infallible,
mem::{self, MaybeUninit},
};
pub fn try_make_table_array<T: Sized, E, const ROW: usize, const COL: usize>(
mut f: impl FnMut(usize, usize) -> Result<T, E>,
) -> Result<[[T; COL]; ROW], E> {
unsafe {
let mut data: MaybeUninit<[[T; COL]; ROW]> = MaybeUninit::uninit();
let data_ptr: *mut [T; COL] = mem::transmute(&mut data);
let mut cnt = 0;
let mut err = None;
for row in 0..ROW {
match try_make_array(|col| f(row, col)) {
Ok(x) => {
data_ptr.add(row).write(x);
cnt += 1;
}
Err(x) => {
err = Some((cnt, x));
break;
}
}
}
if let Some((cnt, x)) = err {
for i in (0..cnt).rev() {
data_ptr.add(i).drop_in_place();
}
return Err(x);
}
Ok(data.assume_init())
}
}
pub fn make_table_array<T: Sized, const ROW: usize, const COL: usize>(
mut f: impl FnMut(usize, usize) -> T,
) -> [[T; COL]; ROW] {
let res: Result<[[T; COL]; ROW], Infallible> = try_make_table_array(|row, col| Ok(f(row, col)));
res.expect("BUG: This should never fail! If you're seeing this, there may be a memory leak!")
}
pub fn default_table_array<T: Default, const ROW: usize, const COL: usize>() -> [[T; COL]; ROW] {
make_table_array(|_, _| T::default())
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq, Eq)]
struct ComplexObj {
row: usize,
col: usize,
text: &'static str,
}
impl ComplexObj {
pub fn new(row: usize, col: usize, text: &'static str) -> Self {
Self { row, col, text }
}
}
#[test]
fn try_make_table_array_should_correctly_initialize_if_all_element_calls_succeed() {
let arr: Result<[[(usize, usize); 3]; 2], Infallible> =
try_make_table_array(|row, col| Ok((row, col)));
let arr = arr.unwrap();
assert_eq!(arr[0][0], (0, 0));
assert_eq!(arr[0][1], (0, 1));
assert_eq!(arr[0][2], (0, 2));
assert_eq!(arr[1][0], (1, 0));
assert_eq!(arr[1][1], (1, 1));
assert_eq!(arr[1][2], (1, 2));
}
#[test]
fn try_make_table_array_should_correctly_deallocate_if_an_element_call_fails() {
let arr: Result<[[(usize, usize); 3]; 2], &'static str> =
try_make_table_array(|row, col| {
if row == 1 && col == 1 {
Err("Failure!")
} else {
Ok((row, col))
}
});
assert_eq!(arr.unwrap_err(), "Failure!");
}
#[test]
fn try_make_table_array_should_support_complex_objects_with_heap_allocations() {
let arr: Result<[[ComplexObj; 3]; 2], Infallible> =
try_make_table_array(|row, col| Ok(ComplexObj::new(row, col, "complex")));
let arr = arr.unwrap();
assert_eq!(arr[0][0], ComplexObj::new(0, 0, "complex"));
assert_eq!(arr[0][1], ComplexObj::new(0, 1, "complex"));
assert_eq!(arr[0][2], ComplexObj::new(0, 2, "complex"));
assert_eq!(arr[1][0], ComplexObj::new(1, 0, "complex"));
assert_eq!(arr[1][1], ComplexObj::new(1, 1, "complex"));
assert_eq!(arr[1][2], ComplexObj::new(1, 2, "complex"));
}
#[test]
fn try_make_table_array_should_support_deallocating_complex_objects_on_failure() {
let arr: Result<[[ComplexObj; 3]; 2], &'static str> = try_make_table_array(|row, col| {
if row == 1 && col == 1 {
Err("Failure!")
} else {
Ok(ComplexObj::new(row, col, "complex"))
}
});
assert_eq!(arr.unwrap_err(), "Failure!");
}
#[test]
fn make_table_array_should_correctly_initialize() {
let arr: [[(usize, usize); 3]; 2] = make_table_array(|row, col| (row, col));
assert_eq!(arr[0][0], (0, 0));
assert_eq!(arr[0][1], (0, 1));
assert_eq!(arr[0][2], (0, 2));
assert_eq!(arr[1][0], (1, 0));
assert_eq!(arr[1][1], (1, 1));
assert_eq!(arr[1][2], (1, 2));
}
#[test]
fn default_table_array_should_correctly_initialize() {
#[derive(Debug, PartialEq, Eq)]
struct MyField(u8);
impl Default for MyField {
fn default() -> Self {
Self(123)
}
}
let arr: [[MyField; 3]; 2] = default_table_array();
assert_eq!(arr[0][0], MyField(123));
assert_eq!(arr[0][1], MyField(123));
assert_eq!(arr[0][2], MyField(123));
assert_eq!(arr[1][0], MyField(123));
assert_eq!(arr[1][1], MyField(123));
assert_eq!(arr[1][2], MyField(123));
}
}