Skip to main content

osom_lib_arrays/fixed_array/
inline_fixed_array.rs

1#![allow(clippy::cast_possible_truncation)]
2
3use core::{
4    borrow::{Borrow, BorrowMut},
5    hash::Hash,
6    marker::PhantomData,
7    mem::{ManuallyDrop, MaybeUninit},
8};
9
10use osom_lib_primitives::length::Length;
11use osom_lib_reprc::traits::ReprC;
12use osom_lib_try_clone::TryClone;
13
14use crate::{
15    errors::{ArrayError, ArrayIsEmptyError, ArrayTryCloneError},
16    traits::{ImmutableArray, MutableArray},
17};
18
19/// A fixed-capacity array. This type is similar to [`DynamicArray`][`crate::dynamic_array::DynamicArray`],
20/// except its capacity is fixed at compile time, and doesn't change at runtime. The "Inline" prefix
21/// indicates that the data is stored inside the struct itself, meaning its size depends on `TSIZE`.
22///
23/// In particular this type does not require an allocator.
24#[repr(C)]
25#[derive(Debug)]
26#[must_use]
27pub struct InlineFixedArray<const TSIZE: usize, T: Sized> {
28    length: Length,
29    inner: MaybeUninit<[T; TSIZE]>,
30    _phantom: PhantomData<T>,
31}
32
33unsafe impl<const TSIZE: usize, T: Send + Sized> Send for InlineFixedArray<TSIZE, T> {}
34unsafe impl<const TSIZE: usize, T: Sync + Sized> Sync for InlineFixedArray<TSIZE, T> {}
35
36unsafe impl<const TSIZE: usize, T: ReprC + Sized> ReprC for InlineFixedArray<TSIZE, T> {
37    const CHECK: () = const {
38        osom_lib_reprc::hidden::is_reprc::<T>();
39        osom_lib_reprc::hidden::is_reprc::<PhantomData<T>>();
40        osom_lib_reprc::hidden::is_reprc::<Length>();
41        osom_lib_reprc::hidden::is_reprc::<MaybeUninit<[T; TSIZE]>>();
42    };
43}
44
45impl<const TSIZE: usize, T: Sized> InlineFixedArray<TSIZE, T> {
46    /// Creates a new, empty [`InlineFixedArray`].
47    ///
48    /// # Panics
49    ///
50    /// This function will panic if the `TSIZE` is invalid, i.e.
51    /// when either `TSIZE` is 0 or exceeds [`Length::MAX_LENGTH`].
52    pub const fn new() -> Self {
53        const {
54            assert!(
55                TSIZE <= Length::MAX_LENGTH.as_usize(),
56                "TSIZE cannot exceed Length::MAX_LENGTH"
57            );
58            assert!(TSIZE > 0, "TSIZE cannot be 0");
59        }
60
61        Self {
62            length: Length::ZERO,
63            inner: MaybeUninit::uninit(),
64            _phantom: PhantomData,
65        }
66    }
67
68    /// Returns the length of the [`InlineFixedArray`].
69    #[inline(always)]
70    pub const fn length(&self) -> Length {
71        self.length
72    }
73
74    #[inline(always)]
75    pub const fn is_empty(&self) -> bool {
76        self.length.as_u32() == 0
77    }
78
79    /// Returns the capacity of the [`InlineFixedArray`]. This is `TSIZE`
80    /// as [`Length`].
81    #[inline(always)]
82    pub const fn capacity(&self) -> Length {
83        unsafe { Length::new_unchecked(TSIZE as u32) }
84    }
85
86    /// Returns the [`InlineFixedArray`] as immutable slice.
87    #[inline(always)]
88    pub const fn as_slice_const(&self) -> &[T] {
89        // In const context we cannot take `.as_ptr()` directly,
90        // and we have to do &raw casts. The following checks are only to
91        // ensure that both types have the same layout. Just in case.
92        const {
93            assert!(
94                size_of::<[T; TSIZE]>() == size_of::<ManuallyDrop<[T; TSIZE]>>(),
95                "T and ManuallyDrop<[T; TSIZE]> must have the same size"
96            );
97            assert!(
98                align_of::<[T; TSIZE]>() == align_of::<ManuallyDrop<[T; TSIZE]>>(),
99                "T and ManuallyDrop<[T; TSIZE]> must have the same alignment"
100            );
101        }
102        let ptr = (&raw const self.inner).cast();
103        unsafe { core::slice::from_raw_parts(ptr, self.length.as_usize()) }
104    }
105
106    /// Returns the [`InlineFixedArray`] as mutable slice.
107    #[inline(always)]
108    pub const fn as_slice_mut_const(&mut self) -> &mut [T] {
109        // In const context we cannot take `.as_ptr()` directly,
110        // and we have to do &raw casts. The following checks are only to
111        // ensure that both types have the same layout. Just in case.
112        const {
113            assert!(
114                size_of::<[T; TSIZE]>() == size_of::<ManuallyDrop<[T; TSIZE]>>(),
115                "T and ManuallyDrop<[T; TSIZE]> must have the same size"
116            );
117            assert!(
118                align_of::<[T; TSIZE]>() == align_of::<ManuallyDrop<[T; TSIZE]>>(),
119                "T and ManuallyDrop<[T; TSIZE]> must have the same alignment"
120            );
121        }
122        let ptr = (&raw mut self.inner).cast();
123        unsafe { core::slice::from_raw_parts_mut(ptr, self.length.as_usize()) }
124    }
125}
126
127impl<const TSIZE: usize, T: Sized> Default for InlineFixedArray<TSIZE, T> {
128    fn default() -> Self {
129        Self::new()
130    }
131}
132
133impl<const TSIZE: usize, T: Sized> ImmutableArray<T> for InlineFixedArray<TSIZE, T> {
134    #[inline(always)]
135    fn length(&self) -> Length {
136        self.length()
137    }
138
139    #[inline(always)]
140    fn capacity(&self) -> Length {
141        self.capacity()
142    }
143}
144
145impl<const TSIZE: usize, T: Sized> MutableArray<T> for InlineFixedArray<TSIZE, T> {
146    fn try_push_array<const TARRSIZE: usize>(&mut self, arr: [T; TARRSIZE]) -> Result<(), ArrayError> {
147        let len = self.length.as_usize();
148        if len + TARRSIZE > TSIZE {
149            return Err(ArrayError::LengthLimitExceeded);
150        }
151
152        unsafe {
153            let mut dst = self.inner.as_mut_ptr().cast::<T>().add(len);
154            let mut src = arr.as_ptr();
155            let end = src.add(TARRSIZE);
156            while src < end {
157                dst.write(src.read());
158                dst = dst.add(1);
159                src = src.add(1);
160            }
161            core::mem::forget(arr);
162            self.length = Length::new_unchecked((len + TARRSIZE) as u32);
163        }
164        Ok(())
165    }
166
167    fn try_push_slice(&mut self, slice: &[T]) -> Result<(), ArrayTryCloneError>
168    where
169        T: TryClone,
170    {
171        let len = self.length.as_usize();
172        let tsize = slice.len();
173        if len + tsize > TSIZE {
174            return Err(ArrayError::LengthLimitExceeded.into());
175        }
176
177        unsafe {
178            let mut dst = self.inner.as_mut_ptr().cast::<T>().add(len);
179            for item in slice {
180                let clone = item.try_clone().map_err(|_| ArrayTryCloneError::ItemCloningError)?;
181                dst.write(clone);
182                dst = dst.add(1);
183            }
184            self.length = Length::new_unchecked((len + tsize) as u32);
185        }
186        Ok(())
187    }
188
189    fn try_pop(&mut self) -> Result<T, ArrayIsEmptyError> {
190        if self.length == Length::ZERO {
191            return Err(ArrayIsEmptyError);
192        }
193
194        let len = self.length.as_u32();
195        let item = unsafe {
196            self.length = Length::new_unchecked(len - 1);
197            self.inner.as_ptr().cast::<T>().add((len - 1) as usize).read()
198        };
199        Ok(item)
200    }
201}
202
203impl<const TSIZE: usize, T: Sized + TryClone> Clone for InlineFixedArray<TSIZE, T> {
204    fn clone(&self) -> Self {
205        self.try_clone().expect("Failed to clone inline fixed array")
206    }
207}
208
209impl<const TSIZE: usize, T: Sized + TryClone> TryClone for InlineFixedArray<TSIZE, T> {
210    type Error = ArrayTryCloneError;
211
212    fn try_clone(&self) -> Result<Self, Self::Error> {
213        let mut new_instance = Self::new();
214        new_instance.try_push_slice(self.as_slice_const())?;
215        Ok(new_instance)
216    }
217}
218
219impl<const TSIZE: usize, T: Sized + PartialEq, Rhs: AsRef<[T]>> PartialEq<Rhs> for InlineFixedArray<TSIZE, T> {
220    fn eq(&self, other: &Rhs) -> bool {
221        self.as_slice_const() == other.as_ref()
222    }
223}
224
225impl<const TSIZE: usize, T: Sized + Eq> Eq for InlineFixedArray<TSIZE, T> {}
226
227impl<const TSIZE: usize, T: Sized + Hash> Hash for InlineFixedArray<TSIZE, T> {
228    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
229        self.as_slice_const().hash(state);
230    }
231}
232
233impl<const TSIZE: usize, T: Sized> AsRef<[T]> for InlineFixedArray<TSIZE, T> {
234    fn as_ref(&self) -> &[T] {
235        self.as_slice_const()
236    }
237}
238
239impl<const TSIZE: usize, T: Sized> AsMut<[T]> for InlineFixedArray<TSIZE, T> {
240    fn as_mut(&mut self) -> &mut [T] {
241        self.as_slice_mut_const()
242    }
243}
244
245impl<const TSIZE: usize, T: Sized> Borrow<[T]> for InlineFixedArray<TSIZE, T> {
246    fn borrow(&self) -> &[T] {
247        self.as_slice_const()
248    }
249}
250
251impl<const TSIZE: usize, T: Sized> BorrowMut<[T]> for InlineFixedArray<TSIZE, T> {
252    fn borrow_mut(&mut self) -> &mut [T] {
253        self.as_slice_mut_const()
254    }
255}
256
257impl<const TSIZE: usize, T: Sized> Drop for InlineFixedArray<TSIZE, T> {
258    fn drop(&mut self) {
259        if !core::mem::needs_drop::<T>() {
260            return;
261        }
262
263        unsafe {
264            let mut start = (&raw mut self.inner).cast::<T>();
265            let end = start.add(self.length.as_usize());
266            while start < end {
267                core::ptr::drop_in_place(start);
268                start = start.add(1);
269            }
270        }
271    }
272}