Skip to main content

osom_lib_arrays/dynamic_array/inline_dynamic_array/
inline_array.rs

1use core::{
2    alloc::Layout,
3    mem::{ManuallyDrop, MaybeUninit},
4    ops::DerefMut,
5    ptr::NonNull,
6};
7
8use osom_lib_alloc::traits::Allocator;
9use osom_lib_primitives::length::Length;
10use osom_lib_reprc::traits::ReprC;
11
12use crate::errors::ArrayError;
13
14/// Represents a dynamic array, where first `TCAPACITY` items are inlined in the struct itself.
15///
16/// In other words, this array allocates memory only when its length exceeds `TCAPACITY`. In which
17/// case it allocates data on heap, and becomes a slightly less efficient variant of
18/// [`DynamicArray`][crate::dynamic_array::DynamicArray].
19///
20/// Similarly to [`DynamicArray`][crate::dynamic_array::DynamicArray] this struct resizes the
21/// underlying buffer by multiplying capacity by `3/2`.
22#[repr(C)]
23#[must_use]
24pub struct InlineDynamicArray<const TCAPACITY: usize, T, TAllocator>
25where
26    T: Sized,
27    TAllocator: Allocator,
28{
29    pub(super) internal: InlineArrayUnion<TCAPACITY, T>,
30    pub(super) size: Length,
31    pub(super) capacity: Length,
32    pub(super) is_inlined: bool,
33    pub(super) allocator: TAllocator,
34}
35
36unsafe impl<const TCAPACITY: usize, T, TAllocator> Send for InlineDynamicArray<TCAPACITY, T, TAllocator>
37where
38    T: Sized + Send,
39    TAllocator: Allocator + Send,
40{
41}
42
43unsafe impl<const TCAPACITY: usize, T, TAllocator> Sync for InlineDynamicArray<TCAPACITY, T, TAllocator>
44where
45    T: Sized + Sync,
46    TAllocator: Allocator + Sync,
47{
48}
49
50impl<const TCAPACITY: usize, T, TAllocator> core::fmt::Debug for InlineDynamicArray<TCAPACITY, T, TAllocator>
51where
52    T: Sized,
53    TAllocator: Allocator,
54{
55    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56        f.debug_struct("InlineDynamicArray")
57            .field("TCAPACITY", &TCAPACITY)
58            .field("internal", &"[INLINE_ARRAY_UNION]")
59            .field("size", &self.size)
60            .field("capacity", &self.capacity)
61            .field("is_inlined", &self.is_inlined)
62            .field("allocator", &self.allocator)
63            .finish()
64    }
65}
66
67unsafe impl<const TCAPACITY: usize, T, TAllocator> ReprC for InlineDynamicArray<TCAPACITY, T, TAllocator>
68where
69    T: Sized + ReprC,
70    TAllocator: Allocator,
71{
72    const CHECK: () = const {
73        osom_lib_reprc::hidden::is_reprc::<T>();
74        osom_lib_reprc::hidden::is_reprc::<TAllocator>();
75        osom_lib_reprc::hidden::is_reprc::<Length>();
76        osom_lib_reprc::hidden::is_reprc::<InlineArrayUnion<TCAPACITY, T>>();
77    };
78}
79
80#[repr(C)]
81pub(super) union InlineArrayUnion<const TCAPACITY: usize, T>
82where
83    T: Sized,
84{
85    pub inlined: ManuallyDrop<MaybeUninit<[T; TCAPACITY]>>,
86    pub ptr: *mut T,
87}
88
89unsafe impl<const TCAPACITY: usize, T> ReprC for InlineArrayUnion<TCAPACITY, T>
90where
91    T: ReprC,
92{
93    const CHECK: () = const {
94        osom_lib_reprc::hidden::is_reprc::<T>();
95        osom_lib_reprc::hidden::is_reprc::<*mut T>();
96        osom_lib_reprc::hidden::is_reprc::<ManuallyDrop<MaybeUninit<[T; TCAPACITY]>>>();
97    };
98}
99
100impl<const TCAPACITY: usize, T, TAllocator> InlineDynamicArray<TCAPACITY, T, TAllocator>
101where
102    T: Sized,
103    TAllocator: Allocator,
104{
105    /// Creates a new empty [`InlineDynamicArray`] with the default `TAllocator`.
106    #[inline(always)]
107    pub fn new() -> Self
108    where
109        TAllocator: Default,
110    {
111        Self::with_allocator(TAllocator::default())
112    }
113
114    /// Creates a new empty [`InlineDynamicArray`] with the given `TAllocator`.
115    #[inline]
116    pub const fn with_allocator(allocator: TAllocator) -> Self {
117        let inlined = ManuallyDrop::new(MaybeUninit::uninit());
118        Self {
119            internal: InlineArrayUnion { inlined },
120            size: Length::ZERO,
121            capacity: static_capacity::<TCAPACITY>(),
122            is_inlined: true,
123            allocator,
124        }
125    }
126
127    /// Creates a new [`InlineDynamicArray`] with the default `TAllocator`. This method allocates memory
128    /// if `capacity` exceeds `TCAPACITY`.
129    ///
130    /// # Errors
131    ///
132    /// For details see [`ArrayError`].
133    #[inline(always)]
134    pub fn with_capacity(capacity: Length) -> Result<Self, ArrayError>
135    where
136        TAllocator: Default,
137    {
138        Self::with_capacity_and_allocator(capacity, TAllocator::default())
139    }
140
141    /// Creates a new [`InlineDynamicArray`] with the given `TAllocator`. This method allocates memory
142    /// if `capacity` exceeds `TCAPACITY`.
143    ///
144    /// # Errors
145    ///
146    /// For details see [`ArrayError`].
147    pub fn with_capacity_and_allocator(capacity: Length, mut allocator: TAllocator) -> Result<Self, ArrayError> {
148        if capacity.as_usize() <= TCAPACITY {
149            return Ok(Self::with_allocator(allocator));
150        }
151
152        if capacity.as_usize() > Length::MAX_LENGTH.as_usize() {
153            return Err(ArrayError::LengthLimitExceeded);
154        }
155
156        let new_ptr = allocator
157            .allocate(Self::layout_for_size(capacity))
158            .map_err(|_| ArrayError::AllocationError)?
159            .cast::<T>();
160
161        Ok(Self {
162            internal: InlineArrayUnion { ptr: new_ptr.as_ptr() },
163            size: Length::ZERO,
164            capacity: capacity,
165            is_inlined: false,
166            allocator: allocator,
167        })
168    }
169
170    pub(super) const fn layout_for_size(size: Length) -> Layout {
171        let tsize = size_of::<T>();
172        let Some(real_size) = tsize.checked_mul(size.as_usize()) else {
173            panic!("Tried to allocate array of size outside of usize range");
174        };
175        unsafe { Layout::from_size_align_unchecked(real_size, align_of::<T>()) }
176    }
177
178    #[inline(always)]
179    pub(super) const fn current_layout(&self) -> Layout {
180        Self::layout_for_size(self.capacity)
181    }
182
183    #[inline(always)]
184    pub(super) fn is_inlined(&self) -> bool {
185        self.is_inlined
186    }
187
188    pub(super) fn current_ptr_mut(&mut self) -> *mut T {
189        unsafe {
190            if self.is_inlined() {
191                self.internal.inlined.deref_mut().as_mut_ptr().cast::<T>()
192            } else {
193                self.internal.ptr
194            }
195        }
196    }
197
198    pub(super) fn as_slice_internal(&self) -> &[T] {
199        unsafe {
200            let ptr = if self.is_inlined() {
201                self.internal.inlined.as_ptr().cast::<T>()
202            } else {
203                self.internal.ptr
204            };
205
206            core::slice::from_raw_parts(ptr, self.size.as_usize())
207        }
208    }
209
210    pub(super) fn as_slice_mut_internal(&mut self) -> &mut [T] {
211        unsafe { core::slice::from_raw_parts_mut(self.current_ptr_mut(), self.size.as_usize()) }
212    }
213
214    #[allow(clippy::cast_possible_truncation)]
215    pub(super) fn reserve_if_needed(&mut self, new_length: u32) -> Result<(), ArrayError> {
216        let capacity = self.capacity.as_u32();
217        if new_length <= capacity {
218            return Ok(());
219        }
220
221        let new_capacity = {
222            let upper_bound = match new_length {
223                0..=11 => 16,
224                v => (u64::from(v) * 3) / 2,
225            };
226            let capped = core::cmp::min(upper_bound, Length::MAX_LENGTH.as_usize() as u64) as u32;
227            unsafe { Length::new_unchecked(capped) }
228        };
229
230        let new_ptr = unsafe {
231            let new_layout = Self::layout_for_size(new_capacity);
232            if self.is_inlined() {
233                let ptr = self
234                    .allocator
235                    .allocate(new_layout)
236                    .map_err(|_| ArrayError::AllocationError)?
237                    .cast::<T>()
238                    .as_ptr();
239                let current_length = self.size.as_usize();
240                if current_length > 0 {
241                    let inlined_ptr = self.internal.inlined.deref_mut().as_mut_ptr().cast::<T>();
242                    ptr.copy_from_nonoverlapping(inlined_ptr, current_length);
243                }
244                self.is_inlined = false;
245                ptr
246            } else {
247                let old_layout = self.current_layout();
248                let raw_ptr = NonNull::new_unchecked(self.internal.ptr);
249                self.allocator
250                    .resize(raw_ptr.cast(), old_layout, new_layout)
251                    .map_err(|_| ArrayError::AllocationError)?
252                    .cast()
253                    .as_ptr()
254            }
255        };
256
257        self.internal = InlineArrayUnion { ptr: new_ptr };
258        self.capacity = new_capacity;
259        Ok(())
260    }
261}
262
263impl<const TCAPACITY: usize, T, TAllocator> Drop for InlineDynamicArray<TCAPACITY, T, TAllocator>
264where
265    T: Sized,
266    TAllocator: Allocator,
267{
268    fn drop(&mut self) {
269        unsafe {
270            if core::mem::needs_drop::<T>() {
271                for item in self.as_slice_mut_internal() {
272                    core::ptr::drop_in_place(item);
273                }
274            }
275
276            if self.is_inlined() {
277                return;
278            }
279
280            let ptr = NonNull::new_unchecked(self.internal.ptr);
281            self.allocator.deallocate(ptr.cast(), self.current_layout());
282        }
283    }
284}
285
286#[inline(always)]
287#[allow(clippy::cast_possible_truncation)]
288pub(super) const fn static_capacity<const TCAPACITY: usize>() -> Length {
289    const {
290        assert!(
291            TCAPACITY > 0,
292            "TCAPACITY cannot be zero. It reduces the InlineArray into a less efficient DynamicArray. Use latter instead."
293        );
294        assert!(
295            TCAPACITY <= Length::MAX_LENGTH.as_usize(),
296            "TCAPACITY cannot exceed Length::MAX_LENGTH"
297        );
298    }
299
300    unsafe { Length::new_unchecked(TCAPACITY as u32) }
301}