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}