atomic_lend_cell/
atomic_counting.rs

1// Allow dead code when the ref-counting feature is not enabled
2#![cfg_attr(not(feature = "ref-counting"), allow(dead_code))]
3
4//! # Atomic Lend Cell
5//! 
6//! A thread-safe container that allows lending references to data across threads
7//! with atomic reference counting for safe resource management.
8//! 
9//! This crate provides two main types:
10//! - `AtomicLendCell<T>`: The owner that contains the data and can lend it out
11//! - `AtomicBorrowCell<T>`: A reference-counted borrow of data that can be freely cloned and sent between threads
12//!
13//! Unlike standard Rust borrowing, `AtomicLendCell` allows multiple threads to access
14//! the same data simultaneously, while ensuring the original value outlives all borrows.
15
16use std::{ops::Deref, sync::atomic::{AtomicUsize, Ordering}};
17
18/// A container that allows thread-safe lending of its contained value
19///
20/// `AtomicLendCell<T>` owns a value of type `T` and maintains an atomic reference count
21/// to track outstanding borrows. It ensures that the value isn't dropped while
22/// borrows exist, panicking if this invariant would be violated.
23pub struct AtomicLendCell<T> {
24    data: T,
25    refcount: AtomicUsize
26}
27
28impl<T> AtomicLendCell<T> {
29    /// Returns a reference to the contained value
30    ///
31    /// This method provides direct access to the value inside the cell without
32    /// incrementing the reference counter.
33    pub fn as_ref(&self) -> &T{
34        &self.data
35    }
36}
37
38impl<T> Deref for AtomicLendCell<T> {
39    type Target = T;
40    /// Dereferences to the contained value
41    ///
42    /// This provides convenient access to the contained value through the dereference operator (*).
43    fn deref(&self) -> &Self::Target {
44        self.as_ref()
45    }
46}
47
48impl<T> Drop for AtomicLendCell<T> {
49    /// Ensures no borrows exist when the cell is dropped
50    ///
51    /// If outstanding borrows exist when the cell is dropped, this will panic
52    /// to prevent use-after-free errors.
53    fn drop(&mut self) {
54        if self.refcount.load(Ordering::Relaxed) > 0 {
55            panic!("An AtomicBorrowCell outlives the AtomicLendCell which issues it!");
56        }
57    }
58}
59
60/// A thread-safe reference to data contained in an `AtomicLendCell`
61///
62/// `AtomicBorrowCell<T>` holds a pointer to data in an `AtomicLendCell<T>` and
63/// automatically decrements the reference count when dropped. It can be safely
64/// cloned, sent between threads, and shared.
65pub struct AtomicBorrowCell<T> {
66    data_ptr: *const T,
67    refcount_ptr: *const AtomicUsize
68}
69
70impl<T> AtomicBorrowCell<T> {
71    /// Returns a reference to the borrowed value
72    ///
73    /// This method provides access to the value inside the original `AtomicLendCell`.
74    pub fn as_ref(&self) -> &T{
75        unsafe {self.data_ptr.as_ref().unwrap()}
76    }
77}
78
79impl<T> Deref for AtomicBorrowCell<T> {
80    type Target = T;
81    /// Dereferences to the borrowed value
82    ///
83    /// This provides convenient access to the borrowed value through the dereference operator (*).
84    fn deref(&self) -> &Self::Target {
85        self.as_ref()
86    }
87}
88
89impl<T> Drop for AtomicBorrowCell<T> {
90    /// Decrements the reference count when the borrow is dropped
91    fn drop(&mut self) {
92        unsafe {
93            self.refcount_ptr.as_ref().unwrap().fetch_sub(1, Ordering::Release);
94        }
95    }
96}
97
98// These trait implementations make `AtomicBorrowCell` safe to send between threads
99unsafe impl<T: Sync> Send for AtomicBorrowCell<T> {}
100unsafe impl<T: Sync> Sync for AtomicBorrowCell<T> {}
101
102impl<T> AtomicLendCell<T> {
103    /// Creates a new `AtomicLendCell` containing the given value
104    ///
105    /// # Examples
106    ///
107    /// ```
108    /// use atomic_lend_cell::AtomicLendCell;
109    ///
110    /// let cell = AtomicLendCell::new(42);
111    /// ```
112    pub fn new(data: T) -> Self {
113        Self {data, refcount: 0.into()}
114    }
115
116    /// Creates a new `AtomicBorrowCell` for the contained value
117    ///
118    /// This increments the internal reference count and returns a borrow that can
119    /// be sent to other threads. The borrow will automatically decrement the
120    /// reference count when dropped.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use atomic_lend_cell::AtomicLendCell;
126    ///
127    /// let cell = AtomicLendCell::new(42);
128    /// let borrow = cell.borrow();
129    ///
130    /// assert_eq!(*borrow, 42);
131    /// ```
132    pub fn borrow(&self) -> AtomicBorrowCell<T> {
133        self.refcount.fetch_add(1, Ordering::Acquire);
134        AtomicBorrowCell {data_ptr: (&self.data) as * const T, refcount_ptr: &self.refcount as * const AtomicUsize}
135    }
136}
137
138impl<'a, T> AtomicLendCell<&'a T> {
139    /// Creates a new `AtomicBorrowCell` that borrows the referenced value directly
140    ///
141    /// This is useful when the `AtomicLendCell` contains a reference, and you want to
142    /// borrow the underlying value rather than the reference itself.
143    pub fn borrow_deref(&'a self) -> AtomicBorrowCell<T> {
144        self.refcount.fetch_add(1, Ordering::Acquire);
145        AtomicBorrowCell {data_ptr: self.data as * const T, refcount_ptr: &self.refcount as * const AtomicUsize}
146    }
147}
148
149impl<T> Clone for AtomicBorrowCell<T> {
150    /// Creates a new `AtomicBorrowCell` that borrows the same value
151    ///
152    /// This increments the reference count in the original `AtomicLendCell`.
153    fn clone(&self) -> Self {
154        let count = unsafe {self.refcount_ptr.as_ref()}.unwrap();
155        count.fetch_add(1, Ordering::SeqCst);
156        AtomicBorrowCell {data_ptr: self.data_ptr, refcount_ptr: self.refcount_ptr}
157    }
158}
159
160#[test]
161/// Tests that borrowing works across threads
162fn test_lambda_borrow(){
163    let x = AtomicLendCell::new(4);
164    let xr = x.borrow();
165    let t1 = std::thread::spawn(move ||{
166        let y = xr.as_ref();
167        println!("{:?}", y);
168    });
169    let xr = x.borrow();
170    let t2 = std::thread::spawn(move ||{
171        let y = xr.as_ref();
172        println!("{:?}", y);
173    });
174    t1.join().unwrap();
175    t2.join().unwrap();
176}