Skip to main content

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