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}