init_array/
lib.rs

1//! A library for initializing arrays itemwise.
2//!
3//! Normally, when using fixed size arrays, you can only initialize them with a const value.
4//! Example:
5//! ```
6//! // Literals work.
7//! let arr = [0; 5];
8//!
9//! // Const values work too.
10//! const STRING: String = String::new();
11//! let arr = [STRING; 5];
12//! ```
13//!
14//! ```compile_fail
15//! // Function calls don't work.
16//! let arr = [computation(); 5];
17//! ```
18//!
19//! there are a few different ways of initializing an array itemwise, including:
20//! * Using an array of [`Option`]s, initializing them all to [`None`] and then initializing each one to [`Some(computation())`](core::option::Option::Some).
21//! * Using a [`Vec`](alloc::vec::Vec) and incrementally pushing items to it.
22//! * Using an array of [`MaybeUninit`]s, gradually initializing them and then transmuting the array. This requires usage of `unsafe` code.
23//!
24//! This crate uses the third method but hides it behind a safe interface, so that no unsafe code is needed on the User end.
25//! It provides three functions to initialize arrays itemwise:
26//! * [`init_array`] to initialize a stack-based fixed-size array.
27//! * [`init_boxed_array`] to initialize a heap-allocated fixed-size array.
28//! * [`init_boxed_slice`] to initialize a heap-allocated dynamically-sized slice.
29//!
30//! There is also a `try_init_array` function which allows to initialize an array fallibly and return the error early.
31//!
32//! If you have the `nightly` feature enabled, you will have access to additional versions of the `init_boxed_...` functions compliant with the new Allocator API.
33//!
34//!
35//! If you turn off the `alloc` feature, which is enabled by default, you can use this crate in a `#[no_std]` context without an allocator.
36//! The crate is fully `#[no_std]` compatible.
37//!
38//! In addition to the 3 functions mentioned above, there are also two extension traits provided, [`ArrayExt`] and [`SliceExt`],
39//! which provide the same functionality as the free functions.
40//!
41//! All of these functions share the property that, if the initialization of any item panics (i.e. if the stack unwinds), all the
42//! already initialized items are dropped, minimizing the risk of a memory leak.
43#![cfg_attr(not(test), no_std)]
44#![cfg_attr(feature = "nightly", feature(allocator_api, maybe_uninit_uninit_array,))]
45
46#[cfg(feature = "alloc")]
47extern crate alloc;
48
49use core::convert::Infallible;
50use core::mem::{forget, transmute_copy, MaybeUninit};
51use core::ptr::{drop_in_place, slice_from_raw_parts_mut};
52
53mod array_ext;
54#[cfg_attr(not(feature = "nightly"), path = "stable.rs")]
55#[cfg_attr(feature = "nightly", path = "nightly.rs")]
56#[cfg(feature = "alloc")]
57mod boxed;
58#[cfg(feature = "alloc")]
59pub use boxed::*;
60
61pub use array_ext::*;
62
63
64/// Try to initialize all the elements in a slice, returning early if
65/// it fails.
66///
67/// For each item in the slice, the given function will be called with the
68/// items index and the result be written into the slice. If the initialization
69/// of any item fails, all previously initialized items will be dropped, meaning
70/// that the entire slice will be uninitialized again, and the error returned by
71/// `f` will be returned from the function.
72///
73/// # Examples
74///
75/// ```
76/// #![feature(maybe_uninit_uninit_array)]
77/// use init_array::try_init_slice;
78/// use core::mem::MaybeUninit;
79///
80/// let mut arr = MaybeUninit::uninit_array::<5>();
81///
82/// assert_eq!(try_init_slice::<usize, usize, _>(&mut arr, |i| Ok(i)), Ok([0, 1, 2, 3, 4].as_mut_slice()));
83/// assert_eq!(try_init_slice::<usize, usize, _>(&mut arr, |i| Err(i)), Err(0));
84/// ```
85#[inline]
86pub fn try_init_slice<T, E, F: FnMut(usize) -> Result<T, E>>(
87    s: &mut [MaybeUninit<T>],
88    mut f: F,
89) -> Result<&mut [T], E> {
90    struct Guard<T> {
91        ptr: *mut T,
92        len: usize,
93    }
94
95    impl<T> Drop for Guard<T> {
96        fn drop(&mut self) {
97            // SAFETY: It is guaranteed that exactly `self.len` items in the slice have been initialized.
98            // `self.ptr` is guaranteed to be valid, even if `T` is a ZST, because it has been passed in
99            // through the slice `s`.
100            unsafe { drop_in_place(slice_from_raw_parts_mut(self.ptr, self.len)) };
101        }
102    }
103
104    let mut guard = Guard { ptr: s.as_mut_ptr() as *mut T, len: 0 };
105
106    for (i, a) in s.iter_mut().enumerate() {
107        // if we early return here, all the already initialzed elements in the slice
108        // will be dropped by the guards Drop impl
109        a.write(f(i)?);
110        guard.len += 1;
111    }
112
113    forget(guard);
114
115    // since we initialized all the elements, pointer casting to a &mut [T]
116    // is sound
117    Ok(unsafe { &mut *(s as *mut [MaybeUninit<T>] as *mut [T]) })
118}
119
120/// Initialize all the elements in the slice with the result of calling the function
121/// with the index in the slice.
122///
123/// If this unwinds, all the already initalized elements will be dropped, minimizing
124/// the risk of accidental resource leaks
125#[inline]
126pub fn init_slice<T, F: FnMut(usize) -> T>(s: &mut [MaybeUninit<T>], mut f: F) -> &mut [T] {
127    unwrap_infallible(try_init_slice(s, |i| Ok(f(i))))
128}
129
130pub(crate) fn unwrap_infallible<T>(r: Result<T, Infallible>) -> T {
131    r.unwrap_or_else(|i| match i {})
132}
133
134/// Initialize a fixed-sized stack-based array.
135///
136/// This function takes in a function, which can use the index in the array to compute the value for the item at that index.
137/// The function needs to implement [`FnMut`], which means it can also carry internal mutable state which persists for all items.
138///
139/// Thanks to the stabilization of `min_const_generics` in Rust 1.51, you can use this function to initialize arrays of any length.
140///
141/// # Examples
142///
143/// ```
144/// use init_array::init_array;
145/// assert_eq!(init_array(|_| 0), [0; 3]);
146///
147/// assert_eq!(init_array(|i| i + 1), [1, 2, 3, 4, 5]);
148///
149/// let mut state = 0;
150///
151/// // arr[i] represents the sum of the first `i + 1` natural numbers.
152/// let arr = init_array(|i| {
153///     state += i + 1;
154///     state
155/// });
156/// assert_eq!(arr, [1, 3, 6, 10, 15]);
157/// ```
158#[inline]
159pub fn init_array<T, F, const N: usize>(mut f: F) -> [T; N]
160where
161    F: FnMut(usize) -> T,
162{
163    unwrap_infallible(try_init_array(|i| Ok(f(i))))
164}
165
166/// Does the same as [`init_array`] but supports early return for fallible
167/// initialization of fixed size arrays.
168///
169/// # Examples
170///
171/// ```
172/// use init_array::try_init_array;
173/// assert_eq!(try_init_array::<usize, usize, _, 5>(|i| Ok(i)), Ok([0, 1, 2, 3, 4]));
174///
175/// assert_eq!(try_init_array::<usize, usize, _, 5>(|i| Err(i)), Err(0));
176#[inline]
177pub fn try_init_array<T, E, F, const N: usize>(f: F) -> Result<[T; N], E>
178where
179    F: FnMut(usize) -> Result<T, E>,
180{
181    // SAFETY: Assuming that `MaybeUninit<MaybeUninit<T>>` is initialized is safe, as the inner `MaybeUninit<T>` still
182    // doesn't guarantee that the `T` is initialized, so assuming that an array of `MaybeUninit`s is initialized is
183    // safe too.
184    let mut arr = unsafe { MaybeUninit::<[MaybeUninit<T>; N]>::uninit().assume_init() };
185
186    try_init_slice(&mut arr, f)?;
187
188    // SAFETY: `init_slice` initializes all the items in the array, so it's safe to transmute it into the initialized type
189    Ok(unsafe { transmute_copy(&arr) })
190}
191
192#[cfg(test)]
193mod tests {
194    use std::{
195        panic::catch_unwind,
196        sync::atomic::{AtomicUsize, Ordering::SeqCst},
197    };
198
199    use super::*;
200
201    #[test]
202    fn array() {
203        assert_eq!(init_array::<_, _, 3>(|_| 0), [0, 0, 0]);
204        assert_eq!(init_array::<_, _, 3>(|i| i), [0, 1, 2]);
205        assert_eq!(init_array::<_, _, 5>(|i| i * i), [0, 1, 4, 9, 16]);
206    }
207
208    #[cfg(feature = "alloc")]
209    #[test]
210    fn boxed_array() {
211        assert_eq!(*init_boxed_array::<_, _, 3>(|_| 0), [0, 0, 0]);
212        assert_eq!(*init_boxed_array::<_, _, 3>(|i| i), [0, 1, 2]);
213        assert_eq!(*init_boxed_array::<_, _, 5>(|i| i * i), [0, 1, 4, 9, 16]);
214    }
215
216    #[cfg(feature = "alloc")]
217    #[test]
218    fn boxed_slice() {
219        assert_eq!(*init_boxed_slice(3, |_| 0), [0, 0, 0]);
220        assert_eq!(*init_boxed_slice(3, |i| i), [0, 1, 2]);
221        assert_eq!(*init_boxed_slice(5, |i| i * i), [0, 1, 4, 9, 16]);
222    }
223
224    #[cfg(feature = "alloc")]
225    #[test]
226    fn readme_example() {
227        let arr = init_array(|i| i * i);
228        assert_eq!(arr, [0, 1, 4, 9, 16]);
229
230        let arr = init_boxed_array(|i| i * i);
231        assert_eq!(arr, Box::new([0, 1, 4, 9, 16]));
232
233        let arr = init_boxed_slice(5, |i| i * i);
234        assert_eq!(&*arr, &[0, 1, 4, 9, 16]);
235
236        let mut state = 0;
237        let arr = init_array(move |i| {
238            state += i + 1;
239            state
240        });
241
242        assert_eq!(arr, [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]);
243    }
244
245    #[test]
246    fn drop() {
247        static COUNTER: AtomicUsize = AtomicUsize::new(0);
248
249        struct Foo;
250
251        impl Foo {
252            fn new() -> Self {
253                COUNTER.fetch_add(1, SeqCst);
254                Self
255            }
256        }
257
258        impl Drop for Foo {
259            fn drop(&mut self) {
260                COUNTER.fetch_sub(1, SeqCst);
261            }
262        }
263
264        let _ = catch_unwind(|| {
265            init_array::<_, _, 10>(|i| {
266                if i == 7 {
267                    assert_eq!(COUNTER.load(SeqCst), 6);
268                    panic!()
269                }
270                Foo::new()
271            });
272            assert_eq!(COUNTER.load(SeqCst), 0);
273        });
274
275        #[cfg(feature = "alloc")]
276        let _ = catch_unwind(|| {
277            init_boxed_array::<_, _, 10>(|i| {
278                if i == 7 {
279                    assert_eq!(COUNTER.load(SeqCst), 6);
280                    panic!()
281                }
282                Foo::new()
283            });
284            assert_eq!(COUNTER.load(SeqCst), 0);
285        });
286
287        #[cfg(feature = "alloc")]
288        let _ = catch_unwind(|| {
289            init_boxed_slice(10, |i| {
290                if i == 7 {
291                    assert_eq!(COUNTER.load(SeqCst), 6);
292                    panic!()
293                }
294                Foo::new()
295            });
296            assert_eq!(COUNTER.load(SeqCst), 0);
297        });
298    }
299}