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}