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 = unsafe {
29 let ptr = &value as *const [T; N] as *const [MaybeUninit<T>; N];
30 let read_value = core::ptr::read(ptr);
31 core::mem::drop(value);
32 read_value
33 };
34 }
35
36 /// Finishes the buffer by writing the remaining values.
37 ///
38 /// This is equivalent to calling [`self.write::<N, 0>(value)`](`Self::write`), except it is slightly
39 /// more ergonomic.
40 pub fn finish(mut self, value: [T; N]) {
41 self.write_impl(value);
42 core::mem::forget(self);
43 }
44
45 /// Writes `L` values to the buffer and returns a new cursor for the remaining `R` values.
46 ///
47 /// This function cannot compile unless `L + R == N`, however it will be able to pass through
48 /// `cargo check`, since the error is not discovered by `rustc` until it tries to instantiate
49 /// the code.
50 pub fn write<const L: usize, const R: usize>(self, value: [T; L]) -> Cursor<'a, T, R> {
51 let (l, r) = self.split::<L, R>();
52 l.finish(value);
53 r
54 }
55
56 unsafe fn into_buf(self) -> &'a mut [MaybeUninit<T>; N] {
57 core::mem::transmute(self)
58 }
59
60 /// Splits the cursor in two.
61 ///
62 /// This function cannot compile unless `L + R == N`, however it will be able to pass through
63 /// `cargo check`, since the error is not discovered by `rustc` until it tries to instantiate
64 /// the code.
65 pub fn split<const L: usize, const R: usize>(self) -> (Cursor<'a, T, L>, Cursor<'a, T, R>) {
66 let buf = unsafe { self.into_buf() };
67 let (l, r) = crate::util::split_mut::<_, N, L, R>(buf);
68 (Cursor { slice: l }, Cursor { slice: r })
69 }
70
71 /// Compile-time assertion that `N == M` to work-around limitations in rust generics.
72 ///
73 /// This is useful if a type-signature requires the function to have a generic size
74 /// argument, but you want compile-time errors when called with the wrong parameter.
75 ///
76 /// # Examples
77 ///
78 /// ```
79 /// fn example<const N: usize>(cursor: array_init_cursor::Cursor<'_, u8, N>) {
80 /// let cursor: array_init_cursor::Cursor<u8, 10> = cursor.assert_size();
81 /// }
82 /// ```
83 pub fn assert_size<const M: usize>(self) -> Cursor<'a, T, M> {
84 let (l, _) = self.split::<M, 0>();
85 l
86 }
87}
88
89impl<'a, T, const N: usize> Drop for Cursor<'a, T, N> {
90 /// Will panic unless cursor has been completely initialized
91 fn drop(&mut self) {
92 if N > 0 {
93 panic!("Cursor still has uninitialized bytes");
94 }
95 }
96}