Skip to main content

infinity_pool/handles/
blind_local.rs

1use std::any::type_name;
2use std::borrow::Borrow;
3use std::cell::RefCell;
4use std::fmt;
5use std::ops::Deref;
6use std::pin::Pin;
7use std::ptr::NonNull;
8use std::rc::Rc;
9
10use crate::{LayoutKey, LocalBlindPoolCore, LocalBlindPooledMut, RawPooled, RawPooledMut};
11
12/// A shared single-threaded reference-counting handle for a pooled object.
13#[doc = include_str!("../../doc/snippets/ref_counted_handle_implications.md")]
14#[doc = include_str!("../../doc/snippets/shared_handle_implications.md")]
15///
16/// # Thread safety
17///
18/// This type is single-threaded.
19pub struct LocalBlindPooled<T: ?Sized> {
20    inner: RawPooled<T>,
21
22    // This gives us our thread-safety characteristics (single-threaded),
23    // overriding those of `RawPooled<T>`. This is expected because we align
24    // with the stricter constraints of the pool itself, even if the underlying
25    // slab storage allows for more flexibility.
26    remover: Rc<Remover>,
27}
28
29impl<T: ?Sized> LocalBlindPooled<T> {
30    #[must_use]
31    pub(crate) fn new(inner: RawPooledMut<T>, key: LayoutKey, core: LocalBlindPoolCore) -> Self {
32        let inner = inner.into_shared();
33
34        let remover = Remover {
35            // SAFETY: This handle is single-threaded, no cross-thread access even if `T: Send`.
36            handle: unsafe { inner.erase_raw() },
37            key,
38            core,
39        };
40
41        Self {
42            inner,
43            remover: Rc::new(remover),
44        }
45    }
46
47    #[doc = include_str!("../../doc/snippets/handle_ptr.md")]
48    #[must_use]
49    #[inline]
50    #[cfg_attr(test, mutants::skip)] // cargo-mutants tries many unviable mutations, wasting precious build minutes.
51    pub fn ptr(&self) -> NonNull<T> {
52        self.inner.ptr()
53    }
54
55    #[doc = include_str!("../../doc/snippets/ref_counted_as_pin.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 as_pin(&self) -> Pin<&T> {
60        // SAFETY: LocalBlindPooled items are always pinned.
61        unsafe { Pin::new_unchecked(self) }
62    }
63
64    /// Casts this handle to reference the target as a trait object.
65    ///
66    /// This method is only intended for use by the [`define_pooled_dyn_cast!`] macro
67    /// for type-safe casting operations.
68    ///
69    /// # Safety
70    ///
71    /// The caller must guarantee that the provided closure's input and output references
72    /// point to the same object.
73    #[doc(hidden)]
74    #[must_use]
75    #[inline]
76    pub unsafe fn __private_cast_dyn_with_fn<U: ?Sized, F>(self, cast_fn: F) -> LocalBlindPooled<U>
77    where
78        F: FnOnce(&T) -> &U,
79    {
80        // SAFETY: Forwarding callback safety guarantees from the caller.
81        // We are a shared handle, so we always have the right to create
82        // shared references to the target of the handle, satisfying that requirement.
83        let new_inner = unsafe { self.inner.__private_cast_dyn_with_fn(cast_fn) };
84
85        LocalBlindPooled {
86            inner: new_inner,
87            remover: self.remover,
88        }
89    }
90
91    /// Erase the type information from this handle, converting it to `LocalBlindPooled<()>`.
92    ///
93    /// This is useful for extending the lifetime of an object in the pool without retaining
94    /// type information. The type-erased handle prevents access to the object but ensures
95    /// it remains in the pool.
96    #[must_use]
97    #[inline]
98    #[cfg_attr(test, mutants::skip)] // All mutations unviable - save some time.
99    pub fn erase(self) -> LocalBlindPooled<()> {
100        LocalBlindPooled {
101            // SAFETY: This handle is single-threaded, no cross-thread access even if `T: Send`.
102            inner: unsafe { self.inner.erase_raw() },
103            remover: self.remover,
104        }
105    }
106}
107
108#[cfg_attr(coverage_nightly, coverage(off))] // No API contract to test.
109impl<T: ?Sized> fmt::Debug for LocalBlindPooled<T> {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        f.debug_struct(type_name::<Self>())
112            .field("inner", &self.inner)
113            .field("remover", &self.remover)
114            .finish()
115    }
116}
117
118impl<T: ?Sized> Deref for LocalBlindPooled<T> {
119    type Target = T;
120
121    #[inline]
122    #[cfg_attr(test, mutants::skip)] // Cargo-mutants does not understand this signature - every mutation is unviable waste of time.
123    fn deref(&self) -> &Self::Target {
124        // SAFETY: This is a shared handle - the only references
125        // that can ever exist are shared references.
126        // We guarantee liveness by being a reference counted handle.
127        unsafe { self.ptr().as_ref() }
128    }
129}
130
131impl<T: ?Sized> Borrow<T> for LocalBlindPooled<T> {
132    #[inline]
133    #[cfg_attr(test, mutants::skip)] // Cargo-mutants does not understand this signature - every mutation is unviable waste of time.
134    fn borrow(&self) -> &T {
135        self
136    }
137}
138
139impl<T: ?Sized> AsRef<T> for LocalBlindPooled<T> {
140    #[inline]
141    #[cfg_attr(test, mutants::skip)] // Cargo-mutants does not understand this signature - every mutation is unviable waste of time.
142    fn as_ref(&self) -> &T {
143        self
144    }
145}
146
147impl<T: ?Sized> Clone for LocalBlindPooled<T> {
148    #[inline]
149    fn clone(&self) -> Self {
150        Self {
151            inner: self.inner,
152            remover: Rc::clone(&self.remover),
153        }
154    }
155}
156
157impl<T: ?Sized> From<LocalBlindPooledMut<T>> for LocalBlindPooled<T> {
158    #[inline]
159    fn from(value: LocalBlindPooledMut<T>) -> Self {
160        value.into_shared()
161    }
162}
163
164/// When dropped, removes an object from a pool.
165#[derive(Debug)]
166struct Remover {
167    handle: RawPooled<()>,
168    key: LayoutKey,
169    core: LocalBlindPoolCore,
170}
171
172impl Drop for Remover {
173    fn drop(&mut self) {
174        let mut core = RefCell::borrow_mut(&self.core);
175
176        let pool = core
177            .get_mut(&self.key)
178            .expect("if the handle still exists, the inner pool must still exist");
179
180        // SAFETY: The remover controls the shared object lifetime and is the only thing
181        // that can remove the item from the pool. We keep the pool alive for as long as any
182        // handle or remover referencing it exists, so the pool must still exist.
183        unsafe {
184            pool.remove(self.handle);
185        }
186    }
187}
188
189#[cfg(test)]
190#[cfg_attr(coverage_nightly, coverage(off))]
191mod tests {
192    use std::borrow::Borrow;
193
194    use static_assertions::{assert_impl_all, assert_not_impl_any};
195
196    use super::*;
197    use crate::{LocalBlindPool, NotSendNotSync, NotSendSync, SendAndSync, SendNotSync};
198
199    assert_not_impl_any!(LocalBlindPooled<SendAndSync>: Send, Sync);
200    assert_not_impl_any!(LocalBlindPooled<SendNotSync>: Send, Sync);
201    assert_not_impl_any!(LocalBlindPooled<NotSendNotSync>: Send, Sync);
202    assert_not_impl_any!(LocalBlindPooled<NotSendSync>: Send, Sync);
203
204    // This is a shared handle, must be cloneable.
205    assert_impl_all!(LocalBlindPooled<SendAndSync>: Clone);
206
207    assert_not_impl_any!(Remover: Send, Sync);
208
209    // Must have a destructor because we need to remove the object on destroy.
210    assert_impl_all!(Remover: Drop);
211
212    #[test]
213    fn as_pin_returns_pinned_reference() {
214        let pool = LocalBlindPool::new();
215        let shared = pool.insert(42_u32).into_shared();
216
217        let pinned = shared.as_pin();
218        assert_eq!(*pinned.get_ref(), 42);
219    }
220
221    #[test]
222    fn deref_returns_reference_to_value() {
223        let pool = LocalBlindPool::new();
224        let shared = pool.insert(42_u32).into_shared();
225
226        assert_eq!(*shared, 42);
227    }
228
229    #[test]
230    fn borrow_returns_reference() {
231        let pool = LocalBlindPool::new();
232        let shared = pool.insert(42_u32).into_shared();
233
234        let borrowed: &u32 = shared.borrow();
235        assert_eq!(*borrowed, 42);
236    }
237
238    #[test]
239    fn as_ref_returns_reference() {
240        let pool = LocalBlindPool::new();
241        let shared = pool.insert(42_u32).into_shared();
242
243        let reference: &u32 = shared.as_ref();
244        assert_eq!(*reference, 42);
245    }
246
247    #[test]
248    fn erase_extends_lifetime() {
249        let pool = LocalBlindPool::new();
250        let shared = pool.insert(42_u32).into_shared();
251
252        let erased = shared.clone().erase();
253
254        assert_eq!(pool.len(), 1);
255        assert_eq!(*shared, 42);
256
257        drop(shared);
258        assert_eq!(pool.len(), 1);
259
260        drop(erased);
261        assert_eq!(pool.len(), 0);
262    }
263
264    #[test]
265    fn clone_creates_independent_handle() {
266        let pool = LocalBlindPool::new();
267        let shared = pool.insert(42_u32).into_shared();
268
269        let cloned = shared.clone();
270        assert_eq!(*cloned, 42);
271
272        drop(shared);
273        assert_eq!(pool.len(), 1);
274        assert_eq!(*cloned, 42);
275
276        drop(cloned);
277        assert_eq!(pool.len(), 0);
278    }
279
280    #[test]
281    fn from_mut_converts_to_shared() {
282        let pool = LocalBlindPool::new();
283        let handle = pool.insert(42_u32);
284
285        let shared: LocalBlindPooled<u32> = LocalBlindPooled::from(handle);
286        assert_eq!(*shared, 42);
287        assert_eq!(pool.len(), 1);
288    }
289
290    // Must have a destructor because we need to remove the object on destroy.
291    assert_impl_all!(Remover: Drop);
292}