unarray/
build.rs

1use crate::{mark_initialized, uninit_buf};
2
3/// Build an array with a function that creates elements based on their index
4///
5/// ```
6/// # use unarray::*;
7/// let array: [usize; 5] = build_array(|i| i * 2);
8/// assert_eq!(array, [0, 2, 4, 6, 8]);
9/// ```
10/// If `f` panics, any already-initialized elements will be dropped **without** running their
11/// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe",
12/// since Rust's notion of "safety" doesn't guarantee destructors are run.
13///
14/// For builder functions which might fail, consider using [`build_array_result`] or
15/// [`build_array_option`]
16pub fn build_array<T, F: FnMut(usize) -> T, const N: usize>(mut f: F) -> [T; N] {
17    let mut result = uninit_buf();
18
19    for (index, slot) in result.iter_mut().enumerate() {
20        let value = f(index);
21        slot.write(value);
22    }
23
24    // SAFETY:
25    // We have iterated over every element in result and called `.write()` on it, so every element
26    // is initialized
27    unsafe { mark_initialized(result) }
28}
29
30/// Build an array with a function that creates elements based on their value, short-circuiting if
31/// any index returns an `Err`
32///
33/// ```
34/// # use unarray::*;
35///
36/// let success: Result<_, ()> = build_array_result(|i| Ok(i * 2));
37/// assert_eq!(success, Ok([0, 2, 4]));
38/// ```
39///
40/// If `f` panics, any already-initialized elements will be dropped **without** running their
41/// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe",
42/// since Rust's notion of "safety" doesn't guarantee destructors are run.
43///
44/// This is similar to the nightly-only [`core::array::try_from_fn`]
45pub fn build_array_result<T, E, F: FnMut(usize) -> Result<T, E>, const N: usize>(
46    mut f: F,
47) -> Result<[T; N], E> {
48    let mut result = uninit_buf();
49
50    for (index, slot) in result.iter_mut().enumerate() {
51        match f(index) {
52            Ok(value) => slot.write(value),
53            Err(e) => {
54                // SAFETY:
55                // We have failed at `index` which is the `index + 1`th element, so the first
56                // `index` elements are safe to drop
57                result
58                    .iter_mut()
59                    .take(index)
60                    .for_each(|slot| unsafe { slot.assume_init_drop() });
61                return Err(e);
62            }
63        };
64    }
65
66    // SAFETY:
67    // We have iterated over every element in result and called `.write()` on it, so every element
68    // is initialized
69    Ok(unsafe { mark_initialized(result) })
70}
71
72/// Build an array with a function that creates elements based on their value, short-circuiting if
73/// any index returns a `None`
74///
75/// ```
76/// # use unarray::*;
77/// let success = build_array_option(|i| Some(i * 2));
78/// assert_eq!(success, Some([0, 2, 4]));
79/// ```
80///
81/// If `f` panics, any already-initialized elements will be dropped **without** running their
82/// `Drop` implmentations, potentially creating resource leaks. Note that this is still "safe",
83/// since Rust's notion of "safety" doesn't guarantee destructors are run.
84///
85/// This is similar to the nightly-only [`core::array::try_from_fn`]
86pub fn build_array_option<T, F: FnMut(usize) -> Option<T>, const N: usize>(
87    mut f: F,
88) -> Option<[T; N]> {
89    let actual_f = |i: usize| -> Result<T, ()> { f(i).ok_or(()) };
90
91    match build_array_result(actual_f) {
92        Ok(array) => Some(array),
93        Err(()) => None,
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use core::sync::atomic::{AtomicUsize, Ordering};
100
101    use super::*;
102
103    #[test]
104    fn test_build_array() {
105        let array = build_array(|i| i * 2);
106        assert_eq!(array, [0, 2, 4]);
107    }
108
109    #[test]
110    fn test_build_array_option() {
111        let array = build_array_option(|i| Some(i * 2));
112        assert_eq!(array, Some([0, 2, 4]));
113
114        let none: Option<[_; 10]> = build_array_option(|i| if i == 5 { None } else { Some(()) });
115        assert_eq!(none, None);
116    }
117
118    #[test]
119    fn test_build_array_result() {
120        let array = build_array_result(|i| Ok::<usize, ()>(i * 2));
121        assert_eq!(array, Ok([0, 2, 4]));
122
123        let err: Result<[_; 10], _> = build_array_result(|i| if i == 5 { Err(()) } else { Ok(()) });
124        assert_eq!(err, Err(()));
125    }
126
127    struct IncrementOnDrop<'a>(&'a AtomicUsize);
128    impl Drop for IncrementOnDrop<'_> {
129        fn drop(&mut self) {
130            self.0.fetch_add(1, Ordering::Relaxed);
131        }
132    }
133
134    #[test]
135    fn result_doesnt_leak_on_err() {
136        let drop_counter = 0.into();
137
138        // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be
139        // called, since the 4th may be in an inconsistent state
140        let _: Result<[_; 5], _> = build_array_result(|i| {
141            if i == 3 {
142                Err(())
143            } else {
144                Ok(IncrementOnDrop(&drop_counter))
145            }
146        });
147
148        assert_eq!(drop_counter.load(Ordering::Relaxed), 3);
149    }
150
151    #[test]
152    fn option_doesnt_leak_on_err() {
153        let drop_counter = 0.into();
154
155        // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be
156        // called, since the 4th may be in an inconsistent state
157        let _: Option<[_; 5]> = build_array_option(|i| {
158            if i == 3 {
159                None
160            } else {
161                Some(IncrementOnDrop(&drop_counter))
162            }
163        });
164
165        assert_eq!(drop_counter.load(Ordering::Relaxed), 3);
166    }
167}