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 static_assertions::{assert_impl_all, assert_not_impl_any};
193
194    use super::*;
195    use crate::{NotSendNotSync, NotSendSync, SendAndSync, SendNotSync};
196
197    assert_not_impl_any!(LocalBlindPooled<SendAndSync>: Send, Sync);
198    assert_not_impl_any!(LocalBlindPooled<SendNotSync>: Send, Sync);
199    assert_not_impl_any!(LocalBlindPooled<NotSendNotSync>: Send, Sync);
200    assert_not_impl_any!(LocalBlindPooled<NotSendSync>: Send, Sync);
201
202    // This is a shared handle, must be cloneable.
203    assert_impl_all!(LocalBlindPooled<SendAndSync>: Clone);
204
205    assert_not_impl_any!(Remover: Send, Sync);
206
207    // Must have a destructor because we need to remove the object on destroy.
208    assert_impl_all!(Remover: Drop);
209}