blind_pool/local_pooled.rs
1use std::fmt;
2use std::ops::Deref;
3use std::pin::Pin;
4use std::rc::Rc;
5
6use crate::{LocalBlindPool, RawPooled};
7
8/// A reference to a value stored in a [`LocalBlindPool`].
9///
10/// This type provides automatic lifetime management for values in the pool.
11/// When the last [`LocalPooled`] instance for a value is dropped, the value
12/// is automatically removed from the pool.
13///
14/// Multiple [`LocalPooled`] instances can reference the same value through
15/// cloning, implementing reference counting semantics.
16///
17/// # Single-threaded Design
18///
19/// This type is designed for single-threaded use and is neither [`Send`] nor [`Sync`].
20///
21/// # Example
22///
23/// ```rust
24/// use blind_pool::LocalBlindPool;
25///
26/// let pool = LocalBlindPool::new();
27/// let value_handle = pool.insert("Test".to_string());
28///
29/// // Access the value through dereferencing.
30/// assert_eq!(*value_handle, "Test".to_string());
31///
32/// // Clone to create additional references.
33/// let cloned_handle = value_handle.clone();
34/// assert_eq!(*cloned_handle, "Test".to_string());
35/// ```
36pub struct LocalPooled<T: ?Sized> {
37 /// The reference-counted inner data containing the actual pooled item and pool handle.
38 inner: Rc<LocalPooledInner<T>>,
39}
40
41/// Internal data structure that manages the lifetime of a locally pooled item.
42///
43/// This is always type-erased to `()` and shared among all typed views of the same item.
44/// It ensures that the item is removed from the pool exactly once when all references are dropped.
45struct LocalPooledRef {
46 /// The type-erased handle to the actual item in the pool.
47 pooled: RawPooled<()>,
48
49 /// A handle to the pool that keeps it alive as long as this item exists.
50 pool: LocalBlindPool,
51}
52
53/// Internal data structure that contains the typed access to the locally pooled item.
54#[non_exhaustive]
55struct LocalPooledInner<T: ?Sized> {
56 /// The typed handle to the actual item in the pool.
57 pooled: RawPooled<T>,
58
59 /// A shared reference to the lifetime manager.
60 lifetime: Rc<LocalPooledRef>,
61}
62
63impl<T: ?Sized> LocalPooledInner<T> {
64 /// Creates a new [`LocalPooledInner<T>`] from a pooled item and pool handle.
65 ///
66 /// This is an internal constructor used when reconstructing pooled values
67 /// after type casting operations via the [`define_pooled_dyn_cast!`] macro.
68 #[must_use]
69 fn new(pooled: RawPooled<T>, pool: LocalBlindPool) -> Self {
70 let lifetime = Rc::new(LocalPooledRef {
71 pooled: pooled.erase(),
72 pool,
73 });
74 Self { pooled, lifetime }
75 }
76
77 /// Creates a new `LocalPooledInner` sharing the lifetime with an existing one.
78 ///
79 /// This is used for type casting operations where we want to create a new typed view
80 /// while sharing the same lifetime management.
81 #[must_use]
82 fn with_shared_lifetime(pooled: RawPooled<T>, lifetime: Rc<LocalPooledRef>) -> Self {
83 Self { pooled, lifetime }
84 }
85}
86
87impl<T: ?Sized> LocalPooled<T> {
88 /// Creates a new [`LocalPooled<T>`] from a pooled item and pool handle.
89 ///
90 /// This is an internal constructor used by [`LocalBlindPool::insert`] and for
91 /// reconstructing pooled values after type casting via the [`define_pooled_dyn_cast!`] macro.
92 #[must_use]
93 pub(crate) fn new(pooled: RawPooled<T>, pool: LocalBlindPool) -> Self {
94 let inner = LocalPooledInner::new(pooled, pool);
95 Self {
96 inner: Rc::new(inner),
97 }
98 }
99
100 /// Erases the type information from this [`LocalPooled<T>`] handle,
101 /// returning a [`LocalPooled<()>`].
102 ///
103 /// This is useful when you want to store handles of different types in the same collection
104 /// or pass them to code that doesn't need to know the specific type.
105 ///
106 /// The returned handle shares the same underlying reference count as the original handle.
107 /// Multiple handles (both typed and type-erased) can coexist for the same pooled item,
108 /// and the item will only be removed from the pool when all handles are dropped.
109 ///
110 /// # Example
111 ///
112 /// ```rust
113 /// use blind_pool::LocalBlindPool;
114 ///
115 /// let pool = LocalBlindPool::new();
116 /// let value_handle = pool.insert("Test".to_string());
117 /// let cloned_handle = value_handle.clone();
118 ///
119 /// // Erase type information while keeping the original handle via clone.
120 /// let erased = value_handle.erase();
121 ///
122 /// // Both handles are valid and refer to the same item.
123 /// assert_eq!(*cloned_handle, "Test".to_string());
124 ///
125 /// // The erased handle shares the same reference count.
126 /// drop(erased);
127 /// assert_eq!(*cloned_handle, "Test".to_string()); // Still accessible via typed handle.
128 /// ```
129 #[must_use]
130 #[inline]
131 pub fn erase(self) -> LocalPooled<()> {
132 // Create a new erased handle sharing the same lifetime manager
133 let erased_pooled = self.inner.pooled.erase();
134 let erased_inner =
135 LocalPooledInner::with_shared_lifetime(erased_pooled, Rc::clone(&self.inner.lifetime));
136
137 LocalPooled {
138 inner: Rc::new(erased_inner),
139 }
140 }
141
142 /// Returns a pointer to the stored value.
143 ///
144 /// This provides direct access to the underlying pointer while maintaining the safety
145 /// guarantees of the pooled reference. The pointer remains valid as long as any
146 /// [`LocalPooled<T>`] handle exists for the same value.
147 ///
148 /// # Example
149 ///
150 /// ```rust
151 /// use blind_pool::LocalBlindPool;
152 ///
153 /// let pool = LocalBlindPool::new();
154 /// let value_handle = pool.insert("Test".to_string());
155 ///
156 /// // Get the pointer to the stored value.
157 /// let ptr = value_handle.ptr();
158 ///
159 /// // SAFETY: The pointer is valid as long as value_handle exists.
160 /// let value = unsafe { ptr.as_ref() };
161 /// assert_eq!(value, "Test");
162 /// ```
163 #[must_use]
164 #[inline]
165 pub fn ptr(&self) -> std::ptr::NonNull<T> {
166 self.inner.pooled.ptr()
167 }
168
169 /// Returns a pinned reference to the value stored in the pool.
170 ///
171 /// Since values in the pool are always pinned (they never move once inserted),
172 /// this method provides safe access to `Pin<&T>` without requiring unsafe code.
173 ///
174 /// # Example
175 ///
176 /// ```rust
177 /// use std::pin::Pin;
178 ///
179 /// use blind_pool::LocalBlindPool;
180 ///
181 /// let pool = LocalBlindPool::new();
182 /// let handle = pool.insert("hello".to_string());
183 ///
184 /// let pinned: Pin<&String> = handle.as_pin();
185 /// assert_eq!(pinned.len(), 5);
186 /// ```
187 #[must_use]
188 #[inline]
189 pub fn as_pin(&self) -> Pin<&T> {
190 // Delegate to the underlying opaque_pool Pooled<T> as_pin implementation.
191 self.inner.pooled.opaque_handle().as_pin()
192 }
193
194 /// Casts this [`LocalPooled<T>`] to a trait object type.
195 ///
196 /// This method converts a pooled value from a concrete type to a trait object
197 /// while preserving the reference counting and pool management semantics.
198 ///
199 /// This method is only intended for use by the [`define_pooled_dyn_cast!`] macro.
200 #[doc(hidden)]
201 #[must_use]
202 #[inline]
203 pub fn __private_cast_dyn_with_fn<U: ?Sized, F>(self, cast_fn: F) -> LocalPooled<U>
204 where
205 F: FnOnce(&T) -> &U,
206 {
207 // Cast the RawPooled to the trait object using the provided function
208 // SAFETY: The lifetime management logic of this pool guarantees that the target item is
209 // still alive in the pool for as long as any handle exists, which it clearly does.
210 // We only ever hand out shared references to the item, so no conflicting `&mut`
211 // exclusive references can exist.
212 let cast_pooled = unsafe { self.inner.pooled.__private_cast_dyn_with_fn(cast_fn) };
213 let cast_inner =
214 LocalPooledInner::with_shared_lifetime(cast_pooled, Rc::clone(&self.inner.lifetime));
215
216 LocalPooled {
217 inner: Rc::new(cast_inner),
218 }
219 }
220}
221
222impl<T: ?Sized> Clone for LocalPooled<T> {
223 /// Creates another handle to the same pooled value.
224 ///
225 /// This increases the reference count for the underlying value. The value will only be
226 /// removed from the pool when all cloned handles are dropped.
227 ///
228 /// # Example
229 ///
230 /// ```rust
231 /// use blind_pool::LocalBlindPool;
232 ///
233 /// let pool = LocalBlindPool::new();
234 /// let value_handle = pool.insert("Test".to_string());
235 ///
236 /// let cloned_handle = value_handle.clone();
237 ///
238 /// // Both handles refer to the same value.
239 /// assert_eq!(*value_handle, *cloned_handle);
240 ///
241 /// // Value remains in pool until all handles are dropped.
242 /// drop(value_handle);
243 /// assert_eq!(*cloned_handle, "Test".to_string()); // Still accessible.
244 /// ```
245 #[inline]
246 fn clone(&self) -> Self {
247 Self {
248 inner: Rc::clone(&self.inner),
249 }
250 }
251}
252
253impl<T: ?Sized> Deref for LocalPooled<T> {
254 type Target = T;
255
256 /// Provides direct access to the value stored in the pool.
257 ///
258 /// This allows the handle to be used as if it were a reference to the stored value.
259 ///
260 /// # Example
261 ///
262 /// ```rust
263 /// use blind_pool::LocalBlindPool;
264 ///
265 /// let pool = LocalBlindPool::new();
266 /// let string_handle = pool.insert("hello".to_string());
267 ///
268 /// // Access string methods directly.
269 /// assert_eq!(string_handle.len(), 5);
270 /// assert!(string_handle.starts_with("he"));
271 /// ```
272 #[inline]
273 fn deref(&self) -> &Self::Target {
274 // Delegate to the underlying opaque_pool Pooled<T> Deref implementation.
275 self.inner.pooled.opaque_handle()
276 }
277}
278
279impl Drop for LocalPooledRef {
280 /// Automatically removes the item from the pool when the last reference is dropped.
281 ///
282 /// This ensures that resources are properly cleaned up without requiring manual intervention.
283 #[inline]
284 fn drop(&mut self) {
285 // We are guaranteed to be the only one executing this drop because Rc ensures
286 // that Drop on the LocalPooledRef is only called once when the last reference is released.
287 // SAFETY: This pooled handle is being consumed by Drop, ensuring it cannot be used again.
288 unsafe {
289 self.pool.remove(&self.pooled);
290 }
291 }
292}
293
294impl<T: ?Sized> fmt::Debug for LocalPooled<T> {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 f.debug_struct("LocalPooled")
297 .field("type_name", &std::any::type_name::<T>())
298 .field("ptr", &self.inner.pooled.ptr())
299 .finish()
300 }
301}
302
303impl<T: ?Sized> fmt::Debug for LocalPooledInner<T> {
304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305 f.debug_struct("LocalPooledInner")
306 .field("type_name", &std::any::type_name::<T>())
307 .field("ptr", &self.pooled.ptr())
308 .field("lifetime", &self.lifetime)
309 .finish()
310 }
311}
312
313impl fmt::Debug for LocalPooledRef {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 f.debug_struct("LocalPooledRef")
316 .field("pooled", &self.pooled)
317 .field("pool", &self.pool)
318 .finish()
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use static_assertions::assert_not_impl_any;
325
326 use super::LocalPooled;
327 use crate::LocalBlindPool;
328
329 #[test]
330 fn single_threaded_assertions() {
331 // LocalPooled<T> should NOT be Send or Sync regardless of T's Send/Sync status
332 // because it uses Rc internally which is not Send/Sync
333 assert_not_impl_any!(LocalPooled<u32>: Send);
334 assert_not_impl_any!(LocalPooled<u32>: Sync);
335 assert_not_impl_any!(LocalPooled<String>: Send);
336 assert_not_impl_any!(LocalPooled<String>: Sync);
337 assert_not_impl_any!(LocalPooled<Vec<u8>>: Send);
338 assert_not_impl_any!(LocalPooled<Vec<u8>>: Sync);
339
340 // Even with non-Send/non-Sync types, LocalPooled should still not be Send/Sync
341 use std::rc::Rc;
342 assert_not_impl_any!(LocalPooled<Rc<u32>>: Send);
343 assert_not_impl_any!(LocalPooled<Rc<u32>>: Sync);
344
345 use std::cell::RefCell;
346 assert_not_impl_any!(LocalPooled<RefCell<u32>>: Send);
347 assert_not_impl_any!(LocalPooled<RefCell<u32>>: Sync);
348
349 // LocalPooled<T> should always be Unpin regardless of T
350 use static_assertions::assert_impl_all;
351 assert_impl_all!(LocalPooled<u32>: Unpin);
352 assert_impl_all!(LocalPooled<String>: Unpin);
353 assert_impl_all!(LocalPooled<Vec<u8>>: Unpin);
354 assert_impl_all!(LocalPooled<RefCell<u32>>: Unpin);
355 assert_impl_all!(LocalPooled<Rc<u32>>: Unpin);
356
357 // Even with non-Unpin types, LocalPooled should still be Unpin
358 use std::marker::PhantomPinned;
359 assert_impl_all!(LocalPooled<PhantomPinned>: Unpin);
360 }
361
362 #[test]
363 fn automatic_cleanup_single_handle() {
364 let pool = LocalBlindPool::new();
365
366 {
367 let _u32_handle = pool.insert(42_u32);
368 assert_eq!(pool.len(), 1);
369 }
370
371 // Item should be automatically removed after drop
372 assert_eq!(pool.len(), 0);
373 assert!(pool.is_empty());
374 }
375
376 #[test]
377 fn automatic_cleanup_multiple_handles() {
378 let pool = LocalBlindPool::new();
379
380 let u32_handle = pool.insert(42_u32);
381 let cloned_handle = u32_handle.clone();
382
383 assert_eq!(pool.len(), 1);
384
385 // Drop first handle - item should remain
386 drop(u32_handle);
387 assert_eq!(pool.len(), 1);
388
389 // Drop second handle - item should be removed
390 drop(cloned_handle);
391 assert_eq!(pool.len(), 0);
392 }
393
394 #[test]
395 fn clone_handles() {
396 let pool = LocalBlindPool::new();
397
398 let value_handle = pool.insert(42_u64);
399 let cloned_handle = value_handle.clone();
400
401 // Both handles should refer to the same value
402 assert_eq!(*value_handle, 42);
403 assert_eq!(*cloned_handle, 42);
404
405 // Modification through one handle should be visible through the other
406 // (Note: we can't actually modify since we only have shared references)
407 assert_eq!(*value_handle, *cloned_handle);
408 }
409
410 #[test]
411 fn string_methods_through_deref() {
412 let pool = LocalBlindPool::new();
413
414 let string_handle = pool.insert("hello world".to_string());
415
416 // Test that we can call String methods directly
417 assert_eq!(string_handle.len(), 11);
418 assert!(string_handle.starts_with("hello"));
419 assert!(string_handle.ends_with("world"));
420 assert!(string_handle.contains("lo wo"));
421 }
422
423 #[test]
424 fn ptr_access() {
425 let pool = LocalBlindPool::new();
426
427 let value_handle = pool.insert(42_u64);
428
429 // Access the value directly through dereferencing
430 assert_eq!(*value_handle, 42);
431
432 // Access the value through the ptr() method
433 let ptr = value_handle.ptr();
434 // SAFETY: The pointer is valid as long as value_handle exists.
435 let value = unsafe { ptr.read() };
436 assert_eq!(value, 42);
437 }
438
439 #[test]
440 fn erase_type_information() {
441 let pool = LocalBlindPool::new();
442
443 let u64_handle = pool.insert(42_u64);
444 let typed_clone = u64_handle.clone();
445 let erased = u64_handle.erase();
446
447 // Verify the typed handle still works
448 assert_eq!(*typed_clone, 42);
449
450 // Pool should still contain the item
451 assert_eq!(pool.len(), 1);
452
453 drop(erased);
454 drop(typed_clone);
455 assert_eq!(pool.len(), 0);
456 }
457
458 #[test]
459 fn erase_with_multiple_references_works() {
460 let pool = LocalBlindPool::new();
461
462 let value_handle = pool.insert(42_u64);
463 let cloned_handle = value_handle.clone();
464
465 // This should now work without panicking
466 let erased = value_handle.erase();
467
468 // Both handles should still work
469 assert_eq!(*cloned_handle, 42);
470
471 // Verify the erased handle is valid by ensuring cleanup works properly
472 drop(erased);
473 assert_eq!(*cloned_handle, 42); // Typed handle should still work
474
475 drop(cloned_handle);
476 assert_eq!(pool.len(), 0);
477 }
478
479 #[test]
480 fn drop_with_types_that_have_drop() {
481 let pool = LocalBlindPool::new();
482
483 // Vec has a non-trivial Drop implementation
484 let vec_handle = pool.insert(vec![1, 2, 3, 4, 5]);
485
486 assert_eq!(vec_handle.len(), 5);
487 assert_eq!(pool.len(), 1);
488
489 drop(vec_handle);
490 assert_eq!(pool.len(), 0);
491 }
492
493 #[test]
494 fn works_with_single_byte_type() {
495 let pool = LocalBlindPool::new();
496
497 let u8_handle = pool.insert(255_u8);
498
499 assert_eq!(*u8_handle, 255);
500 assert_eq!(pool.len(), 1);
501
502 drop(u8_handle);
503 assert_eq!(pool.len(), 0);
504 }
505}