nstd_sys/shared_ptr.rs
1//! A reference counting smart pointer.
2use crate::{
3 core::{
4 alloc::{
5 nstd_core_alloc_layout_align, nstd_core_alloc_layout_new, nstd_core_alloc_layout_size,
6 NSTDAllocLayout, NSTDAllocator,
7 },
8 mem::nstd_core_mem_copy,
9 optional::NSTDOptional,
10 },
11 NSTDAny, NSTDAnyMut, NSTDUInt,
12};
13use nstdapi::nstdapi;
14
15/// The size (in bytes) of [usize].
16const USIZE_SIZE: usize = core::mem::size_of::<usize>();
17
18/// A reference counting smart pointer.
19#[nstdapi]
20pub struct NSTDSharedPtr<'a> {
21 /// The memory allocator.
22 allocator: &'a NSTDAllocator,
23 /// A raw pointer to private data about the shared object.
24 ptr: NSTDAnyMut,
25 /// The shared object's memory layout.
26 layout: NSTDAllocLayout,
27}
28impl NSTDSharedPtr<'_> {
29 /// Returns a copy of the number of pointers sharing the object.
30 #[inline]
31 #[allow(clippy::missing_const_for_fn)]
32 fn ptrs(&self) -> usize {
33 // SAFETY:
34 // - Shared pointers are always non-null.
35 // - Shared pointers never allocate more than `isize::MAX` bytes for their value.
36 unsafe { core::ptr::read_unaligned(self.ptr.add(nstd_shared_ptr_size(self)).cast()) }
37 }
38
39 /// Returns a mutable pointer to the number of pointers sharing the object.
40 ///
41 /// # Note
42 ///
43 /// The returned pointer may be unaligned, so reading/writing must be done with
44 /// [`core::ptr::read_unaligned`] and [`core::ptr::write_unaligned`].
45 #[inline]
46 #[allow(clippy::missing_const_for_fn)]
47 fn ptrs_mut(&self) -> *mut usize {
48 // SAFETY:
49 // - Shared pointers are always non-null.
50 // - Shared pointers never allocate more than `isize::MAX` bytes for their value.
51 unsafe { self.ptr.add(nstd_shared_ptr_size(self)).cast() }
52 }
53}
54impl Drop for NSTDSharedPtr<'_> {
55 /// [`NSTDSharedPtr`]'s destructor.
56 fn drop(&mut self) {
57 // SAFETY: Shared pointers are always non-null.
58 unsafe {
59 // Update the pointer count.
60 let ptrs = self.ptrs_mut();
61 #[allow(clippy::arithmetic_side_effects)]
62 let new_size = self.ptrs() - 1;
63 core::ptr::write_unaligned(ptrs, new_size);
64 // If the pointer count is zero, free the data.
65 if new_size == 0 {
66 (self.allocator.deallocate)(self.allocator.state, self.ptr, self.layout);
67 }
68 }
69 }
70}
71
72/// Represents an optional value of type `NSTDSharedPtr`.
73pub type NSTDOptionalSharedPtr<'a> = NSTDOptional<NSTDSharedPtr<'a>>;
74
75/// Creates a new initialized instance of a shared pointer.
76///
77/// # Parameters:
78///
79/// - `const NSTDAllocator *allocator` - The memory allocator.
80///
81/// - `NSTDAllocLayout layout` - The shared object's memory layout.
82///
83/// - `NSTDAny init` - A pointer to the object to initialize the shared pointer with.
84///
85/// # Returns
86///
87/// `NSTDOptionalSharedPtr shared_ptr` - The new shared pointer, or an uninitialized "none" variant
88/// if allocating fails.
89///
90/// # Safety
91///
92/// `init` must be a pointer to a value that is valid for reads based on `layout`.
93///
94/// # Example
95///
96/// ```
97/// use core::ptr::addr_of;
98/// use nstd_sys::{
99/// alloc::NSTD_ALLOCATOR,
100/// core::alloc::nstd_core_alloc_layout_new,
101/// shared_ptr::{nstd_shared_ptr_get, nstd_shared_ptr_new},
102/// };
103///
104/// unsafe {
105/// let v = i16::MIN;
106/// let size = core::mem::size_of::<i16>();
107/// let align = core::mem::align_of::<i16>();
108/// let layout = nstd_core_alloc_layout_new(size, align).unwrap();
109/// let shared_ptr = nstd_shared_ptr_new(&NSTD_ALLOCATOR, layout, addr_of!(v).cast()).unwrap();
110/// assert!(*nstd_shared_ptr_get(&shared_ptr).cast::<i16>() == v);
111/// }
112/// ```
113#[nstdapi]
114pub unsafe fn nstd_shared_ptr_new(
115 allocator: &NSTDAllocator,
116 layout: NSTDAllocLayout,
117 init: NSTDAny,
118) -> NSTDOptionalSharedPtr<'_> {
119 // Allocate a region of memory for the object and the pointer count.
120 let size = nstd_core_alloc_layout_size(layout);
121 if let Some(buffer_size) = size.checked_add(USIZE_SIZE) {
122 let align = nstd_core_alloc_layout_align(layout);
123 if let NSTDOptional::Some(layout) = nstd_core_alloc_layout_new(buffer_size, align) {
124 let ptr = (allocator.allocate)(allocator.state, layout);
125 if !ptr.is_null() {
126 // Initialize the shared object.
127 nstd_core_mem_copy(ptr.cast(), init.cast(), size);
128 // Set the pointer count to one.
129 let ptrs = ptr.add(size).cast::<usize>();
130 core::ptr::write_unaligned(ptrs, 1);
131 // Construct the pointer.
132 return NSTDOptional::Some(NSTDSharedPtr {
133 allocator,
134 ptr,
135 layout,
136 });
137 }
138 }
139 }
140 NSTDOptional::None
141}
142
143/// Creates a new zero-initialized instance of a shared pointer.
144///
145/// # Parameters:
146///
147/// - `const NSTDAllocator *allocator` - The memory allocator.
148///
149/// - `NSTDAllocLayout layout` - The shared object's memory layout.
150///
151/// # Returns
152///
153/// `NSTDOptionalSharedPtr shared_ptr` - The yet to be shared pointer, or an uninitialized "none"
154/// variant if allocating fails.
155///
156/// # Safety
157///
158/// The data to be stored in the shared pointer must be safely representable by an all-zero byte
159/// pattern.
160///
161/// # Example
162///
163/// ```
164/// use nstd_sys::{
165/// alloc::NSTD_ALLOCATOR,
166/// core::alloc::nstd_core_alloc_layout_new,
167/// shared_ptr::{nstd_shared_ptr_get, nstd_shared_ptr_new_zeroed},
168/// };
169///
170/// unsafe {
171/// let size = core::mem::size_of::<u128>();
172/// let align = core::mem::align_of::<u128>();
173/// let layout = nstd_core_alloc_layout_new(size, align).unwrap();
174/// let shared_ptr = nstd_shared_ptr_new_zeroed(&NSTD_ALLOCATOR, layout).unwrap();
175/// assert!(*nstd_shared_ptr_get(&shared_ptr).cast::<u128>() == 0);
176/// }
177/// ```
178#[nstdapi]
179pub unsafe fn nstd_shared_ptr_new_zeroed(
180 allocator: &NSTDAllocator,
181 layout: NSTDAllocLayout,
182) -> NSTDOptionalSharedPtr<'_> {
183 // Allocate a region of memory for the object and the pointer count.
184 let size = nstd_core_alloc_layout_size(layout);
185 if let Some(buffer_size) = size.checked_add(USIZE_SIZE) {
186 let align = nstd_core_alloc_layout_align(layout);
187 if let NSTDOptional::Some(layout) = nstd_core_alloc_layout_new(buffer_size, align) {
188 let ptr = (allocator.allocate_zeroed)(allocator.state, layout);
189 if !ptr.is_null() {
190 // Set the pointer count to one.
191 let ptrs = ptr.add(size).cast::<usize>();
192 core::ptr::write_unaligned(ptrs, 1);
193 // Construct the pointer.
194 return NSTDOptional::Some(NSTDSharedPtr {
195 allocator,
196 ptr,
197 layout,
198 });
199 }
200 }
201 }
202 NSTDOptional::None
203}
204
205/// Shares `shared_ptr`.
206///
207/// # Parameters:
208///
209/// - `const NSTDSharedPtr *shared_ptr` - The shared object to share.
210///
211/// # Returns
212///
213/// `NSTDSharedPtr shared` - A new pointer pointing to the shared data.
214///
215/// # Example
216///
217/// ```
218/// use core::ptr::addr_of;
219/// use nstd_sys::{
220/// alloc::NSTD_ALLOCATOR,
221/// core::alloc::nstd_core_alloc_layout_new,
222/// shared_ptr::{nstd_shared_ptr_get, nstd_shared_ptr_new, nstd_shared_ptr_share},
223/// };
224///
225/// unsafe {
226/// let v = 39u64;
227/// let share;
228/// {
229/// let size = core::mem::size_of::<u64>();
230/// let align = core::mem::align_of::<u64>();
231/// let layout = nstd_core_alloc_layout_new(size, align).unwrap();
232/// let addr = addr_of!(v).cast();
233/// let shared_ptr = nstd_shared_ptr_new(&NSTD_ALLOCATOR, layout, addr).unwrap();
234/// share = nstd_shared_ptr_share(&shared_ptr);
235/// }
236/// assert!(*nstd_shared_ptr_get(&share).cast::<u64>() == v);
237/// }
238/// ```
239#[inline]
240#[nstdapi]
241pub fn nstd_shared_ptr_share<'a>(shared_ptr: &NSTDSharedPtr<'a>) -> NSTDSharedPtr<'a> {
242 // SAFETY: Shared pointers are always non-null.
243 unsafe {
244 // Update the pointer count.
245 let ptrs = shared_ptr.ptrs_mut();
246 #[allow(clippy::arithmetic_side_effects)]
247 core::ptr::write_unaligned(ptrs, *ptrs + 1);
248 // Construct the new shared pointer instance.
249 NSTDSharedPtr {
250 allocator: shared_ptr.allocator,
251 ptr: shared_ptr.ptr,
252 layout: shared_ptr.layout,
253 }
254 }
255}
256
257/// Returns an immutable reference to a shared object's allocator.
258///
259/// # Parameters:
260///
261/// - `const NSTDSharedPtr *shared_ptr` - The shared object.
262///
263/// # Returns
264///
265/// `const NSTDAllocator *allocator` - The shared object's allocator.
266#[inline]
267#[nstdapi]
268pub const fn nstd_shared_ptr_allocator<'a>(shared_ptr: &NSTDSharedPtr<'a>) -> &'a NSTDAllocator {
269 shared_ptr.allocator
270}
271
272/// Returns the number of pointers that share `shared_ptr`'s data.
273///
274/// # Parameters:
275///
276/// - `const NSTDSharedPtr *shared_ptr` - An instance of a shared pointer.
277///
278/// # Returns
279///
280/// `NSTDUInt owners` - The number of pointers that share `shared_ptr`'s data.
281///
282/// # Example
283///
284/// ```
285/// use core::ptr::addr_of;
286/// use nstd_sys::{
287/// alloc::NSTD_ALLOCATOR,
288/// core::alloc::nstd_core_alloc_layout_new,
289/// shared_ptr::{
290/// nstd_shared_ptr_get, nstd_shared_ptr_new, nstd_shared_ptr_owners, nstd_shared_ptr_share,
291/// },
292/// };
293///
294/// const SIZE: usize = core::mem::size_of::<i128>();
295///
296/// unsafe {
297/// let v = i128::MIN;
298/// let share;
299/// {
300/// let size = core::mem::size_of::<i128>();
301/// let align = core::mem::align_of::<i128>();
302/// let layout = nstd_core_alloc_layout_new(size, align).unwrap();
303/// let addr = addr_of!(v).cast();
304/// let shared_ptr = nstd_shared_ptr_new(&NSTD_ALLOCATOR, layout, addr).unwrap();
305/// assert!(nstd_shared_ptr_owners(&shared_ptr) == 1);
306///
307/// share = nstd_shared_ptr_share(&shared_ptr);
308/// assert!(nstd_shared_ptr_owners(&shared_ptr) == 2);
309///
310/// let temp = nstd_shared_ptr_share(&shared_ptr);
311/// assert!(nstd_shared_ptr_owners(&temp) == 3);
312/// }
313/// assert!(nstd_shared_ptr_owners(&share) == 1);
314/// assert!(*nstd_shared_ptr_get(&share).cast::<i128>() == v);
315/// }
316/// ```
317#[inline]
318#[nstdapi]
319pub fn nstd_shared_ptr_owners(shared_ptr: &NSTDSharedPtr<'_>) -> NSTDUInt {
320 shared_ptr.ptrs()
321}
322
323/// Returns the size of the shared object.
324///
325/// # Parameters:
326///
327/// - `const NSTDSharedPtr *shared_ptr` - The shared pointer.
328///
329/// # Returns
330///
331/// `NSTDUInt size` - The size of the shared object.
332///
333/// # Example
334///
335/// ```
336/// use nstd_sys::{
337/// alloc::NSTD_ALLOCATOR,
338/// core::alloc::nstd_core_alloc_layout_new,
339/// shared_ptr::{nstd_shared_ptr_new_zeroed, nstd_shared_ptr_size},
340/// };
341///
342/// unsafe {
343/// let size = core::mem::size_of::<f64>();
344/// let align = core::mem::align_of::<f64>();
345/// let layout = nstd_core_alloc_layout_new(size, align).unwrap();
346/// let shared_ptr = nstd_shared_ptr_new_zeroed(&NSTD_ALLOCATOR, layout).unwrap();
347/// assert!(nstd_shared_ptr_size(&shared_ptr) == size);
348/// }
349/// ```
350#[inline]
351#[nstdapi]
352#[allow(clippy::arithmetic_side_effects)]
353pub const fn nstd_shared_ptr_size(shared_ptr: &NSTDSharedPtr<'_>) -> NSTDUInt {
354 nstd_core_alloc_layout_size(shared_ptr.layout) - USIZE_SIZE
355}
356
357/// Returns an immutable raw pointer to the shared object.
358///
359/// # Parameters:
360///
361/// - `const NSTDSharedPtr *shared_ptr` - The shared pointer.
362///
363/// # Returns
364///
365/// `NSTDAny ptr` - A raw pointer to the shared object.
366///
367/// # Example
368///
369/// ```
370/// use core::ptr::addr_of;
371/// use nstd_sys::{
372/// alloc::NSTD_ALLOCATOR,
373/// core::alloc::nstd_core_alloc_layout_new,
374/// shared_ptr::{nstd_shared_ptr_get, nstd_shared_ptr_new},
375/// };
376///
377/// unsafe {
378/// let v = u128::MAX;
379/// let size = core::mem::size_of::<u128>();
380/// let align = core::mem::align_of::<u128>();
381/// let layout = nstd_core_alloc_layout_new(size, align).unwrap();
382/// let shared_ptr = nstd_shared_ptr_new(&NSTD_ALLOCATOR, layout, addr_of!(v).cast()).unwrap();
383/// assert!(*nstd_shared_ptr_get(&shared_ptr).cast::<u128>() == v);
384/// }
385/// ```
386#[inline]
387#[nstdapi]
388pub const fn nstd_shared_ptr_get(shared_ptr: &NSTDSharedPtr<'_>) -> NSTDAny {
389 shared_ptr.ptr
390}
391
392/// Frees an instance of `NSTDSharedPtr`.
393///
394/// # Parameters:
395///
396/// - `NSTDSharedPtr shared_ptr` - The shared object to free.
397#[inline]
398#[nstdapi]
399#[allow(
400 unused_variables,
401 clippy::missing_const_for_fn,
402 clippy::needless_pass_by_value
403)]
404pub fn nstd_shared_ptr_free(shared_ptr: NSTDSharedPtr<'_>) {}
405
406/// Frees an instance of `NSTDSharedPtr` after invoking `callback` with the shared object.
407///
408/// # Parameters:
409///
410/// - `NSTDSharedPtr shared_ptr` - The shared object to free.
411///
412/// - `void (*callback)(NSTDAnyMut)` - The shared object's destructor.
413///
414/// # Safety
415///
416/// This operation makes a direct call on a C function pointer (`callback`).
417#[inline]
418#[nstdapi]
419#[allow(clippy::needless_pass_by_value)]
420pub unsafe fn nstd_shared_ptr_drop(
421 shared_ptr: NSTDSharedPtr<'_>,
422 callback: unsafe extern "C" fn(NSTDAnyMut),
423) {
424 callback(shared_ptr.ptr);
425}