unarray/map.rs
1use crate::{mark_initialized, uninit_buf};
2
3/// An extension trait that adds methods to `[T; N]`
4///
5/// This trait provides [`UnarrayArrayExt::map_result`] and [`UnarrayArrayExt::map_option`],
6/// which provide functionality similar to the nightly-only [`array::try_map`]
7pub trait UnarrayArrayExt<T, const N: usize> {
8 /// Maps an array, short-circuiting if any element produces an `Err`
9 ///
10 /// ```
11 /// # use unarray::*;
12 /// let elements = ["123", "234", "345"];
13 /// let mapped = elements.map_result(|s| s.parse());
14 /// assert_eq!(mapped, Ok([123, 234, 345]));
15 /// ```
16 ///
17 /// This function applies `f` to every element. If any element produces an `Err`, the function
18 /// immediately returns that error. Otherwise, it returns `Ok(result)` where `result` contains
19 /// the mapped elements in an array.
20 ///
21 /// This function does not allocate space on the heap
22 ///
23 /// For functions that return an `Option`, consider using [`UnarrayArrayExt::map_option`]
24 fn map_result<S, E>(self, f: impl FnMut(T) -> Result<S, E>) -> Result<[S; N], E>;
25
26 /// Maps an array, short-circuiting if any element produces a `None`
27 ///
28 /// ```
29 /// # use unarray::*;
30 /// fn parse(s: &str) -> Option<bool> {
31 /// match s {
32 /// "true" => Some(true),
33 /// "false" => Some(false),
34 /// _ => None,
35 /// }
36 /// }
37 ///
38 /// let elements = ["true", "false", "true"];
39 /// let mapped = elements.map_option(parse);
40 /// assert_eq!(mapped, Some([true, false, true]));
41 /// ```
42 ///
43 /// This function applies `f` to every element. If any element produces `None`, the function
44 /// immediately returns `None`. Otherwise, it returns `Some(result)` where `result` contains
45 /// the mapped elements in an array.
46 ///
47 /// This function does not allocate space on the heap
48 ///
49 /// For functions that return an `Result`, consider using [`UnarrayArrayExt::map_result`]
50 fn map_option<S>(self, f: impl FnMut(T) -> Option<S>) -> Option<[S; N]>;
51}
52
53impl<T, const N: usize> UnarrayArrayExt<T, N> for [T; N] {
54 fn map_result<S, E>(self, mut f: impl FnMut(T) -> Result<S, E>) -> Result<[S; N], E> {
55 let mut result = uninit_buf();
56
57 // This is quaranteed to loop over every element (or panic), since both `result` and `self` have N elements
58 // If a panic occurs, uninitialized data is never dropped, since `MaybeUninit` wraps its
59 // contained data in `ManuallyDrop`
60 for (index, (item, slot)) in IntoIterator::into_iter(self).zip(&mut result).enumerate() {
61 match f(item) {
62 Ok(s) => slot.write(s),
63 Err(e) => {
64 // SAFETY:
65 // We have failed at `index` which is the `index + 1`th element, so the first
66 // `index` elements are safe to drop
67 result
68 .iter_mut()
69 .take(index)
70 .for_each(|slot| unsafe { slot.assume_init_drop() });
71 return Err(e);
72 }
73 };
74 }
75
76 // SAFETY:
77 // At this point in execution, we have iterated over all elements of `result`. If any
78 // errors were encountered, we would have already returned. So it's safe to remove the
79 // MaybeUninit wrapper
80 Ok(unsafe { mark_initialized(result) })
81 }
82
83 fn map_option<S>(self, mut f: impl FnMut(T) -> Option<S>) -> Option<[S; N]> {
84 // transform to a `Result`-returning function so we can avoid duplicating unsafe code
85 let actual_f = |t: T| -> Result<S, ()> { f(t).ok_or(()) };
86
87 let result: Result<[S; N], ()> = UnarrayArrayExt::map_result(self, actual_f);
88 match result {
89 Ok(result) => Some(result),
90 Err(()) => None,
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use core::{
98 convert::TryInto,
99 sync::atomic::{AtomicUsize, Ordering},
100 };
101
102 use super::UnarrayArrayExt;
103 use crate::testing::array_strategy;
104 use proptest::prelude::*;
105 use test_strategy::proptest;
106
107 #[test]
108 fn test_map_option() {
109 let array = [1, 2, 3];
110 let result = array.map_option(|i| Some(i * 2)).unwrap();
111 assert_eq!(result, [2, 4, 6]);
112 }
113
114 #[test]
115 #[should_panic]
116 fn test_map_option_panic() {
117 let array = [1, 2, 3];
118 array.map_option(|i| {
119 if i > 2 {
120 panic!();
121 }
122
123 Some(i)
124 });
125 }
126
127 #[test]
128 fn test_map_result() {
129 let array = [1, 2, 3];
130 let result: Result<_, ()> = array.map_result(|i| Ok(i * 2));
131 assert_eq!(result.unwrap(), [2, 4, 6]);
132 }
133
134 #[test]
135 #[should_panic]
136 fn test_map_result_panic() {
137 let array = [1, 2, 3];
138 let _ = array.map_result(|i| -> Result<i32, ()> {
139 if i > 2 {
140 panic!();
141 }
142
143 Ok(i)
144 });
145 }
146
147 struct IncrementOnDrop<'a>(&'a AtomicUsize);
148 impl Drop for IncrementOnDrop<'_> {
149 fn drop(&mut self) {
150 self.0.fetch_add(1, Ordering::Relaxed);
151 }
152 }
153
154 #[test]
155 fn map_array_result_doesnt_leak() {
156 let drop_counter = 0.into();
157
158 // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be
159 // called, since the 4th may be in an inconsistent state
160 let _ = [0, 1, 2, 3, 4].map_result(|i| {
161 if i == 3 {
162 Err(())
163 } else {
164 Ok(IncrementOnDrop(&drop_counter))
165 }
166 });
167
168 assert_eq!(drop_counter.load(Ordering::Relaxed), 3);
169 }
170
171 #[test]
172 fn map_array_option_doesnt_leak() {
173 let drop_counter = 0.into();
174
175 // this will successfully create 3 structs, fail on the 4th, we expect 3 drops to be
176 // called, since the 4th may be in an inconsistent state
177 let _ = [0, 1, 2, 3, 4].map_option(|i| {
178 if i == 3 {
179 None
180 } else {
181 Some(IncrementOnDrop(&drop_counter))
182 }
183 });
184
185 assert_eq!(drop_counter.load(Ordering::Relaxed), 3);
186 }
187
188 const LEN: usize = 100;
189
190 #[proptest]
191 #[cfg_attr(miri, ignore)]
192 fn proptest_option_map(#[strategy(array_strategy::<LEN>())] array: [String; LEN]) {
193 let expected = array.iter().map(|s| s.len()).collect::<Vec<_>>();
194 let expected: [usize; LEN] = expected.try_into().unwrap();
195 let result = array.map_option(|s| Some(s.len()));
196 prop_assert_eq!(expected, result.unwrap());
197 }
198
199 #[proptest]
200 #[cfg_attr(miri, ignore)]
201 fn proptest_result_map(#[strategy(array_strategy::<LEN>())] array: [String; LEN]) {
202 let expected = array.iter().map(|s| s.len()).collect::<Vec<_>>();
203 let expected: [usize; LEN] = expected.try_into().unwrap();
204 let result: Result<_, ()> = array.map_result(|s| Ok(s.len()));
205 prop_assert_eq!(expected, result.unwrap());
206 }
207}