infinity_pool/handles/raw.rs
1use std::any::type_name;
2use std::fmt;
3use std::pin::Pin;
4use std::ptr::NonNull;
5
6use crate::{RawPooledMut, SlabHandle};
7
8/// A shared handle to an object in an object pool.
9#[doc = include_str!("../../doc/snippets/raw_handle_implications.md")]
10#[doc = include_str!("../../doc/snippets/shared_handle_implications.md")]
11#[doc = include_str!("../../doc/snippets/shared_raw_handle_implications.md")]
12#[doc = include_str!("../../doc/snippets/nonlocal_handle_thread_safety.md")]
13pub struct RawPooled<T>
14where
15 // We support casting to trait objects, hence `?Sized`.
16 T: ?Sized,
17{
18 /// Index of the slab in the pool. Slabs are guaranteed to stay at the same index unless
19 /// the pool is shrunk (which can only happen when the affected slabs are empty, in which
20 /// case all existing handles are already invalidated).
21 slab_index: usize,
22
23 /// Handle to the object in the slab. This grants us access to the object's pointer
24 /// and allows us to operate on the object (e.g. to remove it or create a reference).
25 ///
26 /// We inherit the thread-safety properties of the slab handle (Send from T, Sync always).
27 slab_handle: SlabHandle<T>,
28}
29
30impl<T: ?Sized> RawPooled<T> {
31 #[must_use]
32 pub(crate) fn new(slab_index: usize, slab_handle: SlabHandle<T>) -> Self {
33 Self {
34 slab_index,
35 slab_handle,
36 }
37 }
38
39 /// Get the index of the slab in the pool.
40 ///
41 /// This is used by the pool itself to identify the slab in which the object resides.
42 #[must_use]
43 pub(crate) fn slab_index(&self) -> usize {
44 self.slab_index
45 }
46
47 /// Get the slab handle for this pool handle.
48 ///
49 /// This is used by the pool itself to perform operations on the object in the slab.
50 #[must_use]
51 pub(crate) fn slab_handle(&self) -> SlabHandle<T> {
52 self.slab_handle
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.slab_handle.ptr()
61 }
62
63 /// Erase the type information from this handle for internal use by remover logic.
64 ///
65 /// # Safety
66 ///
67 /// This method is intended for internal use only and does not enforce trait bounds
68 /// on the type being erased. The caller must guarantee that the type-erased handle
69 /// will not be used in a way that would take advantage of auto traits of `()` that
70 /// the original type `T` did not have (such as `Send`).
71 ///
72 /// For public use, see [`erase()`](Self::erase), which has simplified safety rules
73 /// because it can rely on public API constraints being enforced on all accesses.
74 #[must_use]
75 #[inline]
76 #[cfg_attr(test, mutants::skip)] // cargo-mutants tries many unviable mutations, wasting precious build minutes.
77 pub(crate) unsafe fn erase_raw(self) -> RawPooled<()> {
78 RawPooled {
79 slab_index: self.slab_index,
80 // SAFETY: Forwarding safety guarantees from the caller.
81 slab_handle: unsafe { self.slab_handle.erase() },
82 }
83 }
84
85 #[doc = include_str!("../../doc/snippets/raw_as_pin.md")]
86 #[must_use]
87 #[inline]
88 #[cfg_attr(test, mutants::skip)] // cargo-mutants tries many unviable mutations, wasting precious build minutes.
89 pub unsafe fn as_pin(&self) -> Pin<&T> {
90 // SAFETY: Forwarding safety guarantees from the caller.
91 let as_ref = unsafe { self.as_ref() };
92
93 // SAFETY: Pooled items are always pinned.
94 unsafe { Pin::new_unchecked(as_ref) }
95 }
96
97 #[doc = include_str!("../../doc/snippets/raw_as_ref.md")]
98 #[must_use]
99 #[inline]
100 #[cfg_attr(test, mutants::skip)] // cargo-mutants tries many unviable mutations, wasting precious build minutes.
101 pub unsafe fn as_ref(&self) -> &T {
102 // SAFETY: This is a shared handle, so we cannot guarantee borrow safety. What we
103 // do instead is that we only allow shared references to be created from shared handles,
104 // so the user must explicitly invoke (some other) unsafe code to create an exclusive
105 // reference, at which point they become responsible for deconflicting the references.
106 // Pointer validity requires pool to be alive for the duration of the reference.
107 unsafe { self.ptr().as_ref() }
108 }
109
110 /// Casts this handle to reference the target as a trait object.
111 ///
112 /// This method is only intended for use by the [`define_pooled_dyn_cast!`] macro
113 /// for type-safe casting operations.
114 ///
115 /// # Safety
116 ///
117 /// The caller must guarantee that the provided closure's input and output references
118 /// point to the same object.
119 ///
120 /// The caller must guarantee that the pool will remain alive for the duration the returned
121 /// reference is used.
122 #[doc(hidden)]
123 #[must_use]
124 #[inline]
125 pub unsafe fn __private_cast_dyn_with_fn<U: ?Sized, F>(self, cast_fn: F) -> RawPooled<U>
126 where
127 F: FnOnce(&T) -> &U,
128 {
129 // SAFETY: Forwarding callback safety guarantees from the caller.
130 // We are a shared handle, so we always have the right to create
131 // shared references to the target of the handle, satisfying that requirement.
132 let new_handle = unsafe { self.slab_handle.cast_with(cast_fn) };
133
134 RawPooled {
135 slab_index: self.slab_index,
136 slab_handle: new_handle,
137 }
138 }
139
140 /// Erase the type information from this handle, converting it to `RawPooled<()>`.
141 ///
142 /// This is useful for extending the lifetime of an object in the pool without retaining
143 /// type information. The type-erased handle prevents access to the object but provides
144 /// the capability to type-agnostically remove the object at a later point in time.
145 #[must_use]
146 #[inline]
147 #[cfg_attr(test, mutants::skip)] // All mutations unviable - save some time.
148 pub fn erase(self) -> RawPooled<()> {
149 // SAFETY: The risk is that if `T: !Send` then `Self<()>` would be `Send`. Calling
150 // `pool.remove()` with this handle would move the object across threads to drop it.
151 // However, this cannot actually happen because for this call to be possible, `pool`
152 // would need to at minimum be `Send` (either to move it to a new thread or to wrap in
153 // a mutex and gain `Sync` with an exclusive reference), which is only possible if
154 // `T: Send` to begin with because that is a condition set by the pool itself.
155 unsafe { self.erase_raw() }
156 }
157}
158
159impl<T: ?Sized> Clone for RawPooled<T> {
160 #[inline]
161 fn clone(&self) -> Self {
162 *self
163 }
164}
165
166impl<T: ?Sized> Copy for RawPooled<T> {}
167
168impl<T: ?Sized> fmt::Debug for RawPooled<T> {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 f.debug_struct(type_name::<Self>())
171 .field("slab_index", &self.slab_index)
172 .field("slab_handle", &self.slab_handle)
173 .finish()
174 }
175}
176
177impl<T: ?Sized> From<RawPooledMut<T>> for RawPooled<T> {
178 #[inline]
179 fn from(value: RawPooledMut<T>) -> Self {
180 value.into_shared()
181 }
182}
183
184#[cfg(test)]
185#[allow(clippy::undocumented_unsafe_blocks, reason = "test code, be concise")]
186mod tests {
187 use static_assertions::{assert_impl_all, assert_not_impl_any};
188
189 use super::*;
190 use crate::{NotSendNotSync, NotSendSync, SendAndSync, SendNotSync};
191
192 assert_impl_all!(RawPooled<SendAndSync>: Send, Sync);
193 assert_impl_all!(RawPooled<SendNotSync>: Send, Sync);
194 assert_impl_all!(RawPooled<NotSendNotSync>: Sync);
195 assert_impl_all!(RawPooled<NotSendSync>: Sync);
196
197 assert_not_impl_any!(RawPooled<NotSendNotSync>: Send);
198 assert_not_impl_any!(RawPooled<NotSendSync>: Send);
199
200 // Shared raw handles are just fancy pointers, value objects.
201 assert_impl_all!(RawPooled<SendAndSync>: Copy);
202 assert_not_impl_any!(RawPooled<SendAndSync>: Drop);
203
204 #[test]
205 fn erase_raw_and_public_erase() {
206 use crate::RawPinnedPool;
207
208 let mut pool = RawPinnedPool::<u32>::new();
209 let handle = pool.insert(42);
210 let shared = handle.into_shared();
211
212 // Both erase_raw() and erase() produce the same result.
213 let erased_raw = unsafe { shared.erase_raw() };
214 let erased_public = shared.erase();
215
216 // Both are RawPooled<()> and behave identically.
217 assert_eq!(erased_raw.slab_index(), erased_public.slab_index());
218
219 // SAFETY: Handles are valid and pool is alive.
220 unsafe {
221 pool.remove(erased_raw);
222 }
223
224 assert_eq!(pool.len(), 0);
225 }
226}