infinity_pool/handles/
managed.rs

1use std::any::type_name;
2use std::borrow::Borrow;
3use std::fmt;
4use std::ops::Deref;
5use std::pin::Pin;
6use std::ptr::NonNull;
7use std::sync::Arc;
8
9use parking_lot::Mutex;
10
11use crate::{PooledMut, RawOpaquePoolThreadSafe, RawPooled, RawPooledMut};
12
13// Note that while this is a thread-safe handle, we do not require `T: Send` because
14// we do not want to require every trait we cast into via trait object to be `Send`.
15// It is the responsibility of the pool to ensure that only `Send` objects are inserted.
16
17/// A shared thread-safe reference-counting handle for a pooled object.
18#[doc = include_str!("../../doc/snippets/ref_counted_handle_implications.md")]
19#[doc = include_str!("../../doc/snippets/shared_handle_implications.md")]
20#[doc = include_str!("../../doc/snippets/nonlocal_handle_thread_safety.md")]
21pub struct Pooled<T: ?Sized> {
22    // We inherit our thread-safety traits from this one (Send from T, Sync always).
23    inner: RawPooled<T>,
24
25    remover: Arc<Remover>,
26}
27
28impl<T: ?Sized> Pooled<T> {
29    /// # Safety
30    ///
31    /// Even though the signature does not require `T: Send`, the underlying object must be `Send`.
32    /// The signature does not require it to be compatible with casting to trait objects that do
33    /// not have `Send` as a supertrait.
34    #[must_use]
35    pub(crate) unsafe fn new(
36        inner: RawPooledMut<T>,
37        pool: Arc<Mutex<RawOpaquePoolThreadSafe>>,
38    ) -> Self {
39        let inner = inner.into_shared();
40
41        let remover = Remover {
42            // SAFETY: This is a thread-safe handle, which means it can only work on Send types,
43            // so we can have no risk of `T: !Send` which is the main thing we worry about when
44            // erasing the object type. No issue with `Send` types at all.
45            handle: unsafe { inner.erase_raw() },
46            pool,
47        };
48
49        Self {
50            inner,
51            remover: Arc::new(remover),
52        }
53    }
54
55    #[doc = include_str!("../../doc/snippets/handle_ptr.md")]
56    #[must_use]
57    #[inline]
58    #[cfg_attr(test, mutants::skip)] // cargo-mutants tries many unviable mutations, wasting precious build minutes.
59    pub fn ptr(&self) -> NonNull<T> {
60        self.inner.ptr()
61    }
62
63    #[doc = include_str!("../../doc/snippets/ref_counted_as_pin.md")]
64    #[must_use]
65    #[inline]
66    #[cfg_attr(test, mutants::skip)] // cargo-mutants tries many unviable mutations, wasting precious build minutes.
67    pub fn as_pin(&self) -> Pin<&T> {
68        // SAFETY: Pooled items are always pinned.
69        unsafe { Pin::new_unchecked(self) }
70    }
71
72    /// Casts this handle to reference the target as a trait object.
73    ///
74    /// This method is only intended for use by the [`define_pooled_dyn_cast!`] macro
75    /// for type-safe casting operations.
76    ///
77    /// # Safety
78    ///
79    /// The caller must guarantee that the provided closure's input and output references
80    /// point to the same object.
81    #[doc(hidden)]
82    #[must_use]
83    #[inline]
84    pub unsafe fn __private_cast_dyn_with_fn<U: ?Sized, F>(self, cast_fn: F) -> Pooled<U>
85    where
86        F: FnOnce(&T) -> &U,
87    {
88        // SAFETY: Forwarding callback safety guarantees from the caller.
89        // We are a shared handle, so we always have the right to create
90        // shared references to the target of the handle, satisfying that requirement.
91        let new_inner = unsafe { self.inner.__private_cast_dyn_with_fn(cast_fn) };
92
93        Pooled {
94            inner: new_inner,
95            remover: self.remover,
96        }
97    }
98
99    /// Erase the type information from this handle, converting it to `Pooled<()>`.
100    ///
101    /// This is useful for extending the lifetime of an object in the pool without retaining
102    /// type information. The type-erased handle prevents access to the object but ensures
103    /// it remains in the pool.
104    #[must_use]
105    #[inline]
106    #[cfg_attr(test, mutants::skip)] // All mutations unviable - save some time.
107    pub fn erase(self) -> Pooled<()> {
108        Pooled {
109            // SAFETY: This is a thread-safe handle, which means it can only work on Send types,
110            // so we can have no risk of `T: !Send` which is the main thing we worry about when
111            // erasing the object type. No issue with `Send` types at all.
112            inner: unsafe { self.inner.erase_raw() },
113            remover: self.remover,
114        }
115    }
116}
117
118#[cfg_attr(coverage_nightly, coverage(off))] // No API contract to test.
119impl<T: ?Sized> fmt::Debug for Pooled<T> {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        f.debug_struct(type_name::<Self>())
122            .field("inner", &self.inner)
123            .field("remover", &self.remover)
124            .finish()
125    }
126}
127
128impl<T: ?Sized> Deref for Pooled<T> {
129    type Target = T;
130
131    #[inline]
132    #[cfg_attr(test, mutants::skip)] // Cargo-mutants does not understand this signature - every mutation is unviable waste of time.
133    fn deref(&self) -> &Self::Target {
134        // SAFETY: This is a shared handle - the only references
135        // that can ever exist are shared references.
136        // We guarantee liveness by being a reference counted handle.
137        unsafe { self.ptr().as_ref() }
138    }
139}
140
141impl<T: ?Sized> Borrow<T> for Pooled<T> {
142    #[inline]
143    #[cfg_attr(test, mutants::skip)] // Cargo-mutants does not understand this signature - every mutation is unviable waste of time.
144    fn borrow(&self) -> &T {
145        self
146    }
147}
148
149impl<T: ?Sized> AsRef<T> for Pooled<T> {
150    #[inline]
151    #[cfg_attr(test, mutants::skip)] // Cargo-mutants does not understand this signature - every mutation is unviable waste of time.
152    fn as_ref(&self) -> &T {
153        self
154    }
155}
156
157impl<T: ?Sized> Clone for Pooled<T> {
158    #[inline]
159    fn clone(&self) -> Self {
160        Self {
161            inner: self.inner,
162            remover: Arc::clone(&self.remover),
163        }
164    }
165}
166
167impl<T: ?Sized> From<PooledMut<T>> for Pooled<T> {
168    #[inline]
169    fn from(value: PooledMut<T>) -> Self {
170        value.into_shared()
171    }
172}
173
174/// When dropped, removes an object from a pool.
175struct Remover {
176    handle: RawPooled<()>,
177    pool: Arc<Mutex<RawOpaquePoolThreadSafe>>,
178}
179
180#[cfg_attr(coverage_nightly, coverage(off))] // No API contract to test.
181impl fmt::Debug for Remover {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        f.debug_struct(type_name::<Self>())
184            .field("handle", &self.handle)
185            .field("pool", &"<pool>")
186            .finish()
187    }
188}
189
190impl Drop for Remover {
191    fn drop(&mut self) {
192        let mut pool = self.pool.lock();
193
194        // SAFETY: The remover controls the shared object lifetime and is the only thing
195        // that can remove the item from the pool.
196        unsafe {
197            pool.remove(self.handle);
198        }
199    }
200}
201
202// SAFETY: By default we do not have `Sync` because the handle is not `Sync`. However, the reason
203// for that is because the handle can be used to access the object. As we have a type-erased handle
204// that cannot access anything meaningful, and as the remover is not accessing the object anyway,
205// just removing it from the pool, we can safety glue back the `Sync` label onto the type.
206unsafe impl Sync for Remover {}
207
208#[cfg(test)]
209#[cfg_attr(coverage_nightly, coverage(off))]
210mod tests {
211    use static_assertions::{assert_impl_all, assert_not_impl_any};
212
213    use super::*;
214    use crate::{NotSendNotSync, NotSendSync, SendAndSync, SendNotSync};
215
216    assert_impl_all!(Pooled<SendAndSync>: Send, Sync);
217    assert_impl_all!(Pooled<SendNotSync>: Send, Sync);
218    assert_impl_all!(Pooled<NotSendNotSync>: Sync);
219    assert_impl_all!(Pooled<NotSendSync>: Sync);
220
221    assert_not_impl_any!(Pooled<NotSendNotSync>: Send);
222    assert_not_impl_any!(Pooled<NotSendSync>: Send);
223
224    // This is a shared handle, must be cloneable.
225    assert_impl_all!(Pooled<SendAndSync>: Clone);
226
227    assert_impl_all!(Remover: Send, Sync);
228
229    // Must have a destructor because we need to remove the object on destroy.
230    assert_impl_all!(Remover: Drop);
231
232    #[test]
233    fn erase_extends_lifetime() {
234        use crate::OpaquePool;
235
236        let pool = OpaquePool::with_layout_of::<u32>();
237        let handle = pool.insert(42);
238        let shared = handle.into_shared();
239
240        // Clone one handle and erase it.
241        let erased = shared.clone().erase();
242
243        // Both handles keep the object alive.
244        assert_eq!(pool.len(), 1);
245        assert_eq!(*shared, 42);
246
247        // Drop the typed handle.
248        drop(shared);
249
250        // Object still alive due to erased handle.
251        assert_eq!(pool.len(), 1);
252
253        // Drop erased handle, object is removed.
254        drop(erased);
255        assert_eq!(pool.len(), 0);
256    }
257
258    #[test]
259    fn erase_multiple_clones() {
260        use crate::OpaquePool;
261
262        let pool = OpaquePool::with_layout_of::<String>();
263        let handle = pool.insert(String::from("test"));
264        let shared = handle.into_shared();
265
266        let clone1 = shared.clone();
267        let clone2 = shared.clone();
268        let erased1 = clone1.erase();
269        let erased2 = clone2.erase();
270
271        assert_eq!(pool.len(), 1);
272        assert_eq!(*shared, "test");
273
274        drop(shared);
275        assert_eq!(pool.len(), 1);
276
277        drop(erased1);
278        assert_eq!(pool.len(), 1);
279
280        drop(erased2);
281        assert_eq!(pool.len(), 0);
282    }
283
284    #[test]
285    fn erase_works_with_not_unpin_types() {
286        use std::marker::PhantomPinned;
287
288        use crate::OpaquePool;
289
290        // Type that is Send + Sync but !Unpin
291        struct NotUnpin {
292            #[allow(dead_code, reason = "Field used to give struct non-zero size")]
293            data: i32,
294            _marker: PhantomPinned,
295        }
296
297        let pool = OpaquePool::with_layout_of::<NotUnpin>();
298        let handle = pool.insert(NotUnpin {
299            data: 42,
300            _marker: PhantomPinned,
301        });
302
303        // Erasing !Unpin types now works because the compile-time assertion in
304        // remove_unpin() prevents calling it with ().
305        let erased = handle.into_shared().erase();
306
307        assert_eq!(pool.len(), 1);
308
309        drop(erased);
310        assert_eq!(pool.len(), 0);
311    }
312}