Skip to main content

mtl_foundation/
shared_ptr.rs

1//! Smart pointer for Objective-C objects.
2//!
3//! Corresponds to `Foundation/NSSharedPtr.hpp`.
4//!
5//! # C++ Equivalent
6//!
7//! ```cpp
8//! namespace NS {
9//! template <class _Class>
10//! class SharedPtr {
11//! public:
12//!     SharedPtr();
13//!     ~SharedPtr();
14//!     SharedPtr(std::nullptr_t) noexcept;
15//!     SharedPtr(const SharedPtr<_Class>& other) noexcept;
16//!     SharedPtr(SharedPtr<_Class>&& other) noexcept;
17//!     SharedPtr& operator=(const SharedPtr<_Class>& other);
18//!     SharedPtr& operator=(SharedPtr<_Class>&& other);
19//!     _Class* get() const;
20//!     _Class* operator->() const;
21//!     explicit operator bool() const;
22//!     void reset();
23//!     void detach();
24//! private:
25//!     _Class* m_pObject;
26//! };
27//!
28//! template <class _Class>
29//! SharedPtr<_Class> RetainPtr(_Class* pObject);
30//!
31//! template <class _Class>
32//! SharedPtr<_Class> TransferPtr(_Class* pObject);
33//! }
34//! ```
35
36use std::ffi::c_void;
37use std::marker::PhantomData;
38use std::ops::Deref;
39use std::ptr::NonNull;
40
41use mtl_sys::{msg_send_0, sel};
42
43/// A smart pointer that automatically manages the reference count of an Objective-C object.
44///
45/// C++ equivalent: `NS::SharedPtr<_Class>`
46///
47/// # Ownership
48///
49/// `SharedPtr` owns a reference to the underlying Objective-C object. When the `SharedPtr`
50/// is dropped, it will release the reference. When cloned, it will retain a new reference.
51pub struct SharedPtr<T> {
52    /// The raw pointer to the Objective-C object.
53    ptr: NonNull<c_void>,
54    /// Phantom data to track the type.
55    _marker: PhantomData<T>,
56}
57
58impl<T> SharedPtr<T> {
59    /// Create a null SharedPtr.
60    ///
61    /// Note: Unlike C++, Rust's NonNull cannot be null, so this returns an Option.
62    /// For null representation, use `Option<SharedPtr<T>>`.
63    ///
64    /// C++ equivalent: `SharedPtr()`
65    #[inline]
66    pub fn null() -> Option<Self> {
67        None
68    }
69
70    /// Create a SharedPtr from a raw pointer, taking ownership.
71    ///
72    /// # Safety
73    ///
74    /// - The pointer must be a valid Objective-C object.
75    /// - The caller must ensure the object has a retain count of at least 1.
76    /// - Ownership is transferred to the SharedPtr (no additional retain).
77    ///
78    /// C++ equivalent: Used internally by `TransferPtr`
79    #[inline]
80    pub unsafe fn from_raw(ptr: *mut c_void) -> Option<Self> {
81        NonNull::new(ptr).map(|ptr| Self {
82            ptr,
83            _marker: PhantomData,
84        })
85    }
86
87    /// Get the raw pointer to the Objective-C object.
88    ///
89    /// C++ equivalent: `_Class* get() const`
90    #[inline]
91    pub fn get(&self) -> *mut c_void {
92        self.ptr.as_ptr()
93    }
94
95    /// Get the raw pointer (const version for API compatibility).
96    #[inline]
97    pub fn as_ptr(&self) -> *const c_void {
98        self.ptr.as_ptr()
99    }
100
101    /// Reset this SharedPtr to null, releasing the reference.
102    ///
103    /// C++ equivalent: `void reset()`
104    #[inline]
105    pub fn reset(&mut self) {
106        unsafe {
107            msg_send_0::<()>(self.ptr.as_ptr(), sel!(release));
108        }
109        // Note: In Rust, we can't actually make NonNull null,
110        // so after reset, the SharedPtr should not be used.
111        // The caller should replace it or drop it.
112    }
113
114    /// Detach the SharedPtr from the pointee without releasing.
115    ///
116    /// Returns the raw pointer and prevents the destructor from releasing.
117    ///
118    /// C++ equivalent: `void detach()`
119    #[inline]
120    pub fn detach(self) -> *mut c_void {
121        let ptr = self.ptr.as_ptr();
122        std::mem::forget(self);
123        ptr
124    }
125
126    /// Convert to a pointer to a different type.
127    ///
128    /// # Safety
129    ///
130    /// The types must be compatible.
131    #[inline]
132    pub unsafe fn cast<U>(self) -> SharedPtr<U> {
133        let ptr = self.ptr;
134        std::mem::forget(self);
135        SharedPtr {
136            ptr,
137            _marker: PhantomData,
138        }
139    }
140}
141
142impl<T> Clone for SharedPtr<T> {
143    /// Clone this SharedPtr, retaining the object.
144    ///
145    /// C++ equivalent: `SharedPtr(const SharedPtr<_Class>& other)`
146    #[inline]
147    fn clone(&self) -> Self {
148        unsafe {
149            let _: *mut c_void = msg_send_0(self.ptr.as_ptr(), sel!(retain));
150        }
151        Self {
152            ptr: self.ptr,
153            _marker: PhantomData,
154        }
155    }
156}
157
158impl<T> Drop for SharedPtr<T> {
159    /// Release the reference when dropped.
160    ///
161    /// C++ equivalent: `~SharedPtr()`
162    #[inline]
163    fn drop(&mut self) {
164        unsafe {
165            msg_send_0::<()>(self.ptr.as_ptr(), sel!(release));
166        }
167    }
168}
169
170impl<T> Deref for SharedPtr<T> {
171    type Target = *mut c_void;
172
173    #[inline]
174    fn deref(&self) -> &Self::Target {
175        // Return a reference to the pointer itself
176        // This is a bit awkward, but matches the C++ operator-> semantics
177        unsafe { &*((&self.ptr) as *const NonNull<c_void> as *const *mut c_void) }
178    }
179}
180
181impl<T> PartialEq for SharedPtr<T> {
182    #[inline]
183    fn eq(&self, other: &Self) -> bool {
184        self.ptr == other.ptr
185    }
186}
187
188impl<T> Eq for SharedPtr<T> {}
189
190impl<T> std::fmt::Debug for SharedPtr<T> {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        f.debug_struct("SharedPtr").field("ptr", &self.ptr).finish()
193    }
194}
195
196// SAFETY: Objective-C objects with proper reference counting are thread-safe
197unsafe impl<T: Send> Send for SharedPtr<T> {}
198unsafe impl<T: Sync> Sync for SharedPtr<T> {}
199
200/// Create a SharedPtr by retaining an existing raw pointer.
201///
202/// Increases the reference count of the passed-in object.
203///
204/// C++ equivalent: `NS::SharedPtr<_Class> RetainPtr(_Class* pObject)`
205///
206/// # Safety
207///
208/// The pointer must be a valid Objective-C object with a retain count >= 1.
209#[inline]
210pub unsafe fn retain_ptr<T>(ptr: *mut c_void) -> Option<SharedPtr<T>> {
211    if ptr.is_null() {
212        return None;
213    }
214
215    // Retain the object
216    let _: *mut c_void = unsafe { msg_send_0(ptr, sel!(retain)) };
217
218    unsafe { SharedPtr::from_raw(ptr) }
219}
220
221/// Create a SharedPtr by transferring ownership of an existing raw pointer.
222///
223/// Does not increase the reference count - ownership is transferred to SharedPtr.
224///
225/// C++ equivalent: `NS::SharedPtr<_Class> TransferPtr(_Class* pObject)`
226///
227/// # Safety
228///
229/// - The pointer must be a valid Objective-C object.
230/// - The object must have a retain count >= 1.
231/// - The caller transfers ownership and must not release the object.
232#[inline]
233pub unsafe fn transfer_ptr<T>(ptr: *mut c_void) -> Option<SharedPtr<T>> {
234    unsafe { SharedPtr::from_raw(ptr) }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_shared_ptr_size() {
243        // SharedPtr should be pointer-sized (no vtable, just NonNull + PhantomData)
244        assert_eq!(
245            std::mem::size_of::<SharedPtr<()>>(),
246            std::mem::size_of::<*mut c_void>()
247        );
248    }
249}