1#![warn(clippy::clone_on_ref_ptr)]
38
39use std::{
40 cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut},
41 hash::{Hash, Hasher},
42 rc::{Rc, Weak},
43};
44
45#[repr(transparent)]
47#[derive(Debug)]
48pub struct SharedCell<T>(Rc<RefCell<T>>);
49
50impl<T> Clone for SharedCell<T> {
51 fn clone(&self) -> Self {
52 Self(Rc::clone(&self.0))
53 }
54}
55
56impl<T> SharedCell<T> {
57 #[inline]
59 pub fn new(value: T) -> Self {
60 Self(Rc::new(RefCell::new(value)))
61 }
62
63 #[inline]
65 #[must_use]
66 pub fn downgrade(&self) -> WeakCell<T> {
67 WeakCell(Rc::downgrade(&self.0))
68 }
69
70 #[inline]
72 #[must_use]
73 pub fn borrow(&self) -> Ref<'_, T> {
74 self.0.borrow()
75 }
76
77 #[inline]
79 #[must_use]
80 pub fn borrow_mut(&self) -> RefMut<'_, T> {
81 self.0.borrow_mut()
82 }
83
84 #[inline]
90 pub fn try_borrow(&self) -> Result<Ref<'_, T>, BorrowError> {
91 self.0.try_borrow()
92 }
93
94 #[inline]
100 pub fn try_borrow_mut(&self) -> Result<RefMut<'_, T>, BorrowMutError> {
101 self.0.try_borrow_mut()
102 }
103
104 #[inline]
106 #[must_use]
107 pub fn strong_count(&self) -> usize {
108 Rc::strong_count(&self.0)
109 }
110
111 #[inline]
113 #[must_use]
114 pub fn weak_count(&self) -> usize {
115 Rc::weak_count(&self.0)
116 }
117
118 #[inline]
120 #[must_use]
121 pub fn as_ptr(&self) -> *const RefCell<T> {
122 Rc::as_ptr(&self.0)
123 }
124
125 #[inline]
130 pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
131 f(&self.0.borrow())
132 }
133
134 #[inline]
139 pub fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
140 f(&mut self.0.borrow_mut())
141 }
142}
143
144impl<T> PartialEq for SharedCell<T> {
145 fn eq(&self, other: &Self) -> bool {
147 Rc::ptr_eq(&self.0, &other.0)
148 }
149}
150
151impl<T> Eq for SharedCell<T> {}
152
153impl<T> Hash for SharedCell<T> {
154 fn hash<H: Hasher>(&self, state: &mut H) {
156 Rc::as_ptr(&self.0).hash(state);
157 }
158}
159
160impl<T> From<Rc<RefCell<T>>> for SharedCell<T> {
161 fn from(inner: Rc<RefCell<T>>) -> Self {
162 Self(inner)
163 }
164}
165
166impl<T> From<SharedCell<T>> for Rc<RefCell<T>> {
167 fn from(shared: SharedCell<T>) -> Self {
168 shared.0
169 }
170}
171
172impl<T> std::ops::Deref for SharedCell<T> {
173 type Target = Rc<RefCell<T>>;
174
175 fn deref(&self) -> &Self::Target {
176 &self.0
177 }
178}
179
180#[repr(transparent)]
182#[derive(Debug)]
183pub struct WeakCell<T>(Weak<RefCell<T>>);
184
185impl<T> Clone for WeakCell<T> {
186 fn clone(&self) -> Self {
187 Self(Weak::clone(&self.0))
188 }
189}
190
191impl<T> WeakCell<T> {
192 #[inline]
194 pub fn upgrade(&self) -> Option<SharedCell<T>> {
195 self.0.upgrade().map(SharedCell)
196 }
197
198 #[inline]
200 #[must_use]
201 pub fn is_dropped(&self) -> bool {
202 self.0.strong_count() == 0
203 }
204}
205
206impl<T> From<Weak<RefCell<T>>> for WeakCell<T> {
207 fn from(inner: Weak<RefCell<T>>) -> Self {
208 Self(inner)
209 }
210}
211
212impl<T> From<WeakCell<T>> for Weak<RefCell<T>> {
213 fn from(cell: WeakCell<T>) -> Self {
214 cell.0
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use std::collections::HashSet;
221
222 use rstest::rstest;
223
224 use super::*;
225
226 #[rstest]
227 fn test_shared_cell_new_and_borrow() {
228 let cell = SharedCell::new(42);
229 assert_eq!(*cell.borrow(), 42);
230 }
231
232 #[rstest]
233 fn test_shared_cell_borrow_mut() {
234 let cell = SharedCell::new(0);
235 *cell.borrow_mut() = 99;
236 assert_eq!(*cell.borrow(), 99);
237 }
238
239 #[rstest]
240 fn test_shared_cell_clone_shares_value() {
241 let cell = SharedCell::new(10);
242 let clone = SharedCell::clone(&cell);
243 *cell.borrow_mut() = 20;
244 assert_eq!(*clone.borrow(), 20);
245 }
246
247 #[rstest]
248 fn test_shared_cell_strong_weak_counts() {
249 let cell = SharedCell::new(1);
250 assert_eq!(cell.strong_count(), 1);
251 assert_eq!(cell.weak_count(), 0);
252
253 let weak = cell.downgrade();
254 assert_eq!(cell.weak_count(), 1);
255 assert_eq!(cell.strong_count(), 1);
256
257 let clone = SharedCell::clone(&cell);
258 assert_eq!(cell.strong_count(), 2);
259 drop(clone);
260 assert_eq!(cell.strong_count(), 1);
261 drop(weak);
262 assert_eq!(cell.weak_count(), 0);
263 }
264
265 #[rstest]
266 fn test_weak_cell_upgrade_succeeds_while_alive() {
267 let cell = SharedCell::new(10);
268 let weak = cell.downgrade();
269 assert!(!weak.is_dropped());
270
271 let upgraded = weak.upgrade();
272 assert!(upgraded.is_some());
273 assert_eq!(*upgraded.unwrap().borrow(), 10);
274 }
275
276 #[rstest]
277 fn test_weak_cell_upgrade_fails_after_drop() {
278 let weak = {
279 let cell = SharedCell::new(10);
280 cell.downgrade()
281 };
282 assert!(weak.is_dropped());
283 assert!(weak.upgrade().is_none());
284 }
285
286 #[rstest]
287 #[expect(clippy::redundant_clone, reason = "Clone is the behavior under test")]
288 fn test_weak_cell_clone() {
289 let cell = SharedCell::new(5);
290 let weak1 = cell.downgrade();
291 let weak2 = weak1.clone();
292 assert_eq!(cell.weak_count(), 2);
293 assert_eq!(*weak2.upgrade().unwrap().borrow(), 5);
294 }
295
296 #[rstest]
297 fn test_try_borrow_fails_while_mutably_borrowed() {
298 let cell = SharedCell::new(0);
299 let _guard = cell.borrow_mut();
300 assert!(cell.try_borrow().is_err());
301 }
302
303 #[rstest]
304 fn test_try_borrow_mut_fails_while_borrowed() {
305 let cell = SharedCell::new(0);
306 let _guard = cell.borrow();
307 assert!(cell.try_borrow_mut().is_err());
308 }
309
310 #[rstest]
311 fn test_from_rc_refcell_roundtrip() {
312 let rc = Rc::new(RefCell::new(5));
313 let cell = SharedCell::from(rc);
314 assert_eq!(*cell.borrow(), 5);
315
316 let back: Rc<RefCell<i32>> = cell.into();
317 assert_eq!(*back.borrow(), 5);
318 }
319
320 #[rstest]
321 fn test_from_weak_refcell_roundtrip() {
322 let rc = Rc::new(RefCell::new(7));
323 let weak_cell = WeakCell::from(Rc::downgrade(&rc));
324 assert_eq!(*weak_cell.upgrade().unwrap().borrow(), 7);
325
326 let back: Weak<RefCell<i32>> = weak_cell.into();
327 assert_eq!(*back.upgrade().unwrap().borrow(), 7);
328 }
329
330 #[rstest]
331 fn test_partial_eq_is_pointer_identity() {
332 let a = SharedCell::new(10);
333 let b = SharedCell::clone(&a);
334 let c = SharedCell::new(10);
335
336 assert_eq!(a, b);
337 assert_ne!(a, c);
338 }
339
340 #[rstest]
341 #[expect(
342 clippy::mutable_key_type,
343 reason = "SharedCell hashes by pointer identity, not interior value"
344 )]
345 fn test_hash_matches_pointer_identity() {
346 let a = SharedCell::new(10);
347 let b = SharedCell::clone(&a);
348 let c = SharedCell::new(10);
349
350 let mut set: HashSet<SharedCell<i32>> = HashSet::new();
351 set.insert(a);
352 assert!(set.contains(&b));
353 assert!(!set.contains(&c));
354 }
355
356 #[rstest]
357 fn test_as_ptr_matches_clone() {
358 let cell = SharedCell::new(0);
359 let cloned = SharedCell::clone(&cell);
360 assert_eq!(cell.as_ptr(), cloned.as_ptr());
361 }
362
363 #[rstest]
364 fn test_with_drops_borrow_before_returning() {
365 let cell = SharedCell::new(100);
366 let value = cell.with(|v| *v);
367
368 *cell.borrow_mut() = 1;
370
371 assert_eq!(value, 100);
372 assert_eq!(*cell.borrow(), 1);
373 }
374
375 #[rstest]
376 fn test_with_mut_drops_borrow_before_returning() {
377 let cell = SharedCell::new(0);
378 let returned = cell.with_mut(|v| {
379 *v = 7;
380 *v
381 });
382
383 assert_eq!(returned, 7);
385 assert_eq!(*cell.borrow(), 7);
386 }
387}