array_init_cursor/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! Small crate for initializing an uninitialized slice
#![no_std]

use core::mem::MaybeUninit;

mod util;

/// A fixed-size cursor for initializing [`MaybeUninit`] arrays
///
/// The cursor will guarantee that all values have been
/// initialized when the value is dropped, which means
/// that it is safe to call [`MaybeUninit::assume_init()`].
///
/// **NOTE:** This guarantee only holds as long as [`Drop::drop()`] is called.
///           If the value goes out of scope without drop being called (e.g. because
///           of [`core::mem::forget()`]), then this guarantee no longer applies.
pub struct Cursor<'a, T, const N: usize> {
    slice: &'a mut [MaybeUninit<T>; N],
}

impl<'a, T, const N: usize> Cursor<'a, T, N> {
    /// Creates a new cursor.
    pub fn new(slice: &'a mut [MaybeUninit<T>; N]) -> Self {
        Self { slice }
    }

    fn write_impl(&mut self, value: [T; N]) {
        *self.slice = unsafe {
            let ptr = &value as *const [T; N] as *const [MaybeUninit<T>; N];
            let read_value = core::ptr::read(ptr);
            core::mem::drop(value);
            read_value
        };
    }

    /// Finishes the buffer by writing the remaining values.
    ///
    /// This is equivalent to calling [`self.write::<N, 0>(value)`](`Self::write`), except it is slightly
    /// more ergonomic.
    pub fn finish(mut self, value: [T; N]) {
        self.write_impl(value);
        core::mem::forget(self);
    }

    /// Writes `L` values to the buffer and returns a new cursor for the remaining `R` values.
    ///
    /// This function cannot compile unless `L + R == N`, however it will be able to pass through
    /// `cargo check`, since the error is not discovered by `rustc` until it tries to instantiate
    /// the code.
    pub fn write<const L: usize, const R: usize>(self, value: [T; L]) -> Cursor<'a, T, R> {
        let (l, r) = self.split::<L, R>();
        l.finish(value);
        r
    }

    unsafe fn into_buf(self) -> &'a mut [MaybeUninit<T>; N] {
        core::mem::transmute(self)
    }

    /// Splits the cursor in two.
    ///
    /// This function cannot compile unless `L + R == N`, however it will be able to pass through
    /// `cargo check`, since the error is not discovered by `rustc` until it tries to instantiate
    /// the code.
    pub fn split<const L: usize, const R: usize>(self) -> (Cursor<'a, T, L>, Cursor<'a, T, R>) {
        let buf = unsafe { self.into_buf() };
        let (l, r) = crate::util::split_mut::<_, N, L, R>(buf);
        (Cursor { slice: l }, Cursor { slice: r })
    }

    /// Compile-time assertion that `N == M` to work-around limitations in rust generics.
    ///
    /// This is useful if a type-signature requires the function to have a generic size
    /// argument, but you want compile-time errors when called with the wrong parameter.
    ///
    /// # Examples
    ///
    /// ```
    /// fn example<const N: usize>(cursor: array_init_cursor::Cursor<'_, u8, N>) {
    ///     let cursor: array_init_cursor::Cursor<u8, 10> = cursor.assert_size();
    /// }
    /// ```
    pub fn assert_size<const M: usize>(self) -> Cursor<'a, T, M> {
        let (l, _) = self.split::<M, 0>();
        l
    }
}

impl<'a, T, const N: usize> Drop for Cursor<'a, T, N> {
    /// Will panic unless cursor has been completely initialized
    fn drop(&mut self) {
        if N > 0 {
            panic!("Cursor still has uninitialized bytes");
        }
    }
}