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}