osom_lib_arrays/dynamic_array/inline_dynamic_array/
inline_array.rs1use 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#[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 #[inline(always)]
107 pub fn new() -> Self
108 where
109 TAllocator: Default,
110 {
111 Self::with_allocator(TAllocator::default())
112 }
113
114 #[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 #[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 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}