use crate::{mark_initialized, uninit_buf}; /// Build an array with a function that creates elements based on their index /// /// ``` /// # use unarray::*; /// let array: [usize; 5] = build_array(|i| i * 2); /// assert_eq!(array, [0, 2, 4, 6, 8]); /// ``` /// If `f` panics, any already-initialized elements will be dropped **without** running their /// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe", /// since Rust's notion of "safety" doesn't guarantee destructors are run. /// /// For builder functions which might fail, consider using [`build_array_result`] or /// [`build_array_option`] pub fn build_array T, const N: usize>(mut f: F) -> [T; N] { let mut result = uninit_buf(); for (index, slot) in result.iter_mut().enumerate() { let value = f(index); slot.write(value); } // SAFETY: // We have iterated over every element in result and called `.write()` on it, so every element // is initialized unsafe { mark_initialized(result) } } /// Build an array with a function that creates elements based on their value, short-circuiting if /// any index returns an `Err` /// /// ``` /// # use unarray::*; /// /// let success: Result<_, ()> = build_array_result(|i| Ok(i * 2)); /// assert_eq!(success, Ok([0, 2, 4])); /// ``` /// /// If `f` panics, any already-initialized elements will be dropped **without** running their /// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe", /// since Rust's notion of "safety" doesn't guarantee destructors are run. /// /// This is similar to the nightly-only [`core::array::try_from_fn`] pub fn build_array_result Result, const N: usize>( mut f: F, ) -> Result<[T; N], E> { let mut result = uninit_buf(); for (index, slot) in result.iter_mut().enumerate() { match f(index) { Ok(value) => slot.write(value), Err(e) => { // SAFETY: // We have failed at `index` which is the `index + 1`th element, so the first // `index` elements are safe to drop result .iter_mut() .take(index) .for_each(|slot| unsafe { slot.assume_init_drop() }); return Err(e); } }; } // SAFETY: // We have iterated over every element in result and called `.write()` on it, so every element // is initialized Ok(unsafe { mark_initialized(result) }) } /// Build an array with a function that creates elements based on their value, short-circuiting if /// any index returns a `None` /// /// ``` /// # use unarray::*; /// let success = build_array_option(|i| Some(i * 2)); /// assert_eq!(success, Some([0, 2, 4])); /// ``` /// /// If `f` panics, any already-initialized elements will be dropped **without** running their /// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe", /// since Rust's notion of "safety" doesn't guarantee destructors are run. /// /// This is similar to the nightly-only [`core::array::try_from_fn`] pub fn build_array_option Option, const N: usize>( mut f: F, ) -> Option<[T; N]> { let actual_f = |i: usize| -> Result { f(i).ok_or(()) }; match build_array_result(actual_f) { Ok(array) => Some(array), Err(()) => None, } } #[cfg(test)] mod tests { use core::sync::atomic::{AtomicUsize, Ordering}; use super::*; #[test] fn test_build_array() { let array = build_array(|i| i * 2); assert_eq!(array, [0, 2, 4]); } #[test] fn test_build_array_option() { let array = build_array_option(|i| Some(i * 2)); assert_eq!(array, Some([0, 2, 4])); let none: Option<[_; 10]> = build_array_option(|i| if i == 5 { None } else { Some(()) }); assert_eq!(none, None); } #[test] fn test_build_array_result() { let array = build_array_result(|i| Ok::(i * 2)); assert_eq!(array, Ok([0, 2, 4])); let err: Result<[_; 10], _> = build_array_result(|i| if i == 5 { Err(()) } else { Ok(()) }); assert_eq!(err, Err(())); } struct IncrementOnDrop<'a>(&'a AtomicUsize); impl Drop for IncrementOnDrop<'_> { fn drop(&mut self) { self.0.fetch_add(1, Ordering::Relaxed); } } #[test] fn result_doesnt_leak_on_err() { let drop_counter = 0.into(); // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be // called, since the 4th may be in an inconsistent state let _: Result<[_; 5], _> = build_array_result(|i| { if i == 3 { Err(()) } else { Ok(IncrementOnDrop(&drop_counter)) } }); assert_eq!(drop_counter.load(Ordering::Relaxed), 3); } #[test] fn option_doesnt_leak_on_err() { let drop_counter = 0.into(); // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be // called, since the 4th may be in an inconsistent state let _: Option<[_; 5]> = build_array_option(|i| { if i == 3 { None } else { Some(IncrementOnDrop(&drop_counter)) } }); assert_eq!(drop_counter.load(Ordering::Relaxed), 3); } }