array_init_cursor/
lib.rs

1//! Small crate for initializing an uninitialized slice
2#![no_std]
3
4use core::mem::MaybeUninit;
5
6mod util;
7
8/// A fixed-size cursor for initializing [`MaybeUninit`] arrays
9///
10/// The cursor will guarantee that all values have been
11/// initialized when the value is dropped, which means
12/// that it is safe to call [`MaybeUninit::assume_init()`].
13///
14/// **NOTE:** This guarantee only holds as long as [`Drop::drop()`] is called.
15///           If the value goes out of scope without drop being called (e.g. because
16///           of [`core::mem::forget()`]), then this guarantee no longer applies.
17pub struct Cursor<'a, T, const N: usize> {
18    slice: &'a mut [MaybeUninit<T>; N],
19}
20
21impl<'a, T, const N: usize> Cursor<'a, T, N> {
22    /// Creates a new cursor.
23    pub fn new(slice: &'a mut [MaybeUninit<T>; N]) -> Self {
24        Self { slice }
25    }
26
27    fn write_impl(&mut self, value: [T; N]) {
28        *self.slice = value.map(|v| MaybeUninit::new(v));
29    }
30
31    /// Finishes the buffer by writing the remaining values.
32    ///
33    /// This is equivalent to calling [`self.write::<N, 0>(value)`](`Self::write`), except it is slightly
34    /// more ergonomic.
35    pub fn finish(mut self, value: [T; N]) {
36        self.write_impl(value);
37        core::mem::forget(self);
38    }
39
40    /// Writes `L` values to the buffer and returns a new cursor for the remaining `R` values.
41    ///
42    /// This function cannot compile unless `L + R == N`, however it will be able to pass through
43    /// `cargo check`, since the error is not discovered by `rustc` until it tries to instantiate
44    /// the code.
45    pub fn write<const L: usize, const R: usize>(self, value: [T; L]) -> Cursor<'a, T, R> {
46        let (l, r) = self.split::<L, R>();
47        l.finish(value);
48        r
49    }
50
51    fn into_buf(self) -> &'a mut [MaybeUninit<T>; N] {
52        unsafe { core::mem::transmute(self) }
53    }
54
55    /// Splits the cursor in two.
56    ///
57    /// This function cannot compile unless `L + R == N`, however it will be able to pass through
58    /// `cargo check`, since the error is not discovered by `rustc` until it tries to instantiate
59    /// the code.
60    pub fn split<const L: usize, const R: usize>(self) -> (Cursor<'a, T, L>, Cursor<'a, T, R>) {
61        let buf = self.into_buf();
62        let (l, r) = crate::util::split_mut::<_, N, L, R>(buf);
63        (Cursor { slice: l }, Cursor { slice: r })
64    }
65
66    /// Compile-time assertion that `N == M` to work-around limitations in rust generics.
67    ///
68    /// This is useful if a type-signature requires the function to have a generic size
69    /// argument, but you want compile-time errors when called with the wrong parameter.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// fn example<const N: usize>(cursor: array_init_cursor::Cursor<'_, u8, N>) {
75    ///     let cursor: array_init_cursor::Cursor<u8, 10> = cursor.assert_size();
76    /// }
77    /// ```
78    pub fn assert_size<const M: usize>(self) -> Cursor<'a, T, M> {
79        let (l, _) = self.split::<M, 0>();
80        l
81    }
82}
83
84impl<T, const N: usize> Drop for Cursor<'_, T, N> {
85    /// Will panic unless cursor has been completely initialized
86    fn drop(&mut self) {
87        if N > 0 {
88            panic!("Cursor still has uninitialized bytes");
89        }
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use core::sync::atomic::AtomicU8;
96
97    use super::*;
98
99    #[test]
100    fn test_drop() {
101        struct DropCounter<'a>(&'a AtomicU8);
102        impl core::ops::Drop for DropCounter<'_> {
103            fn drop(&mut self) {
104                self.0.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
105            }
106        }
107
108        let value = AtomicU8::new(0);
109        {
110            let mut data: [MaybeUninit<DropCounter<'_>>; 1] = [MaybeUninit::uninit()];
111            Cursor::new(&mut data).finish([DropCounter(&value)]);
112        }
113        assert_eq!(value.load(core::sync::atomic::Ordering::SeqCst), 0);
114
115        let value = AtomicU8::new(0);
116        {
117            let mut data: [MaybeUninit<DropCounter<'_>>; 2] =
118                [MaybeUninit::uninit(), MaybeUninit::uninit()];
119            Cursor::new(&mut data).finish([DropCounter(&value), DropCounter(&value)]);
120        }
121        assert_eq!(value.load(core::sync::atomic::Ordering::SeqCst), 0);
122
123        let value = AtomicU8::new(0);
124        {
125            let mut data: [MaybeUninit<DropCounter<'_>>; 1] = [MaybeUninit::uninit()];
126            Cursor::new(&mut data).finish([DropCounter(&value)]);
127            let [value] = data;
128            unsafe { value.assume_init() };
129        }
130        assert_eq!(value.load(core::sync::atomic::Ordering::SeqCst), 1);
131
132        let value = AtomicU8::new(0);
133        {
134            let mut data: [MaybeUninit<DropCounter<'_>>; 2] =
135                [MaybeUninit::uninit(), MaybeUninit::uninit()];
136            Cursor::new(&mut data).finish([DropCounter(&value), DropCounter(&value)]);
137            let [value0, value1] = data;
138            unsafe { value0.assume_init() };
139            unsafe { value1.assume_init() };
140        }
141        assert_eq!(value.load(core::sync::atomic::Ordering::SeqCst), 2);
142    }
143
144    #[test]
145    fn test_initalized() {
146        let mut data: [MaybeUninit<u8>; 4] = [MaybeUninit::new(0); 4];
147        Cursor::new(&mut data).write([1, 2]).finish([3, 4]);
148        assert_eq!(data.map(|d| unsafe { d.assume_init() }), [1, 2, 3, 4]);
149    }
150}