shared_container/
lib.rs

1//! # Shared Container Module
2//!
3//! This module provides a unified abstraction over different container types
4//! used for shared data access with interior mutability in different contexts.
5//!
6//! It abstracts over the differences between thread-safe `Arc<RwLock<T>>` used in
7//! multi-threaded environments and `Rc<RefCell<T>>` used in single-threaded
8//! environments like WebAssembly.
9//!
10//! This allows code using these containers to be written once but work efficiently
11//! in both contexts.
12
13use std::fmt::Debug;
14use std::ops::{Deref, DerefMut};
15
16// Native platforms use thread-safe types
17#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
18use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak};
19
20// WebAssembly uses single-threaded types
21#[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
22use std::cell::{Ref, RefCell, RefMut};
23#[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
24use std::rc::{Rc, Weak};
25
26
27/// A unified container for shared data that works in both multi-threaded and single-threaded environments.
28///
29/// This struct provides an abstraction over `Arc<RwLock<T>>` (used in multi-threaded environments)
30/// and `Rc<RefCell<T>>` (used in single-threaded environments like WebAssembly).
31///
32/// It allows code to be written once but compile to the most efficient implementation
33/// based on the environment where it will run.
34#[derive(Debug)]
35pub struct SharedContainer<T: Debug> {
36    // Thread-safe implementation for native platforms
37    #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
38    inner: Arc<RwLock<T>>,
39
40    // Single-threaded implementation for WebAssembly
41    #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
42    inner: Rc<RefCell<T>>,
43}
44
45// Implement Send and Sync for SharedContainer only for non-wasm builds
46#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
47unsafe impl<T: Debug + Send> Send for SharedContainer<T> {}
48
49#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
50unsafe impl<T: Debug + Send + Sync> Sync for SharedContainer<T> {}
51
52/// A weak reference to a `SharedContainer`.
53///
54/// This struct provides an abstraction over `Weak<RwLock<T>>` (used in multi-threaded environments)
55/// and `Weak<RefCell<T>>` (used in single-threaded environments like WebAssembly).
56///
57/// Weak references don't prevent the value from being dropped when no strong references
58/// remain. This helps break reference cycles that could cause memory leaks.
59#[derive(Debug)]
60pub struct WeakSharedContainer<T: Debug> {
61    // Thread-safe implementation for native platforms
62    #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
63    inner: Weak<RwLock<T>>,
64
65    // Single-threaded implementation for WebAssembly
66    #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
67    inner: Weak<RefCell<T>>,
68}
69
70impl<T: Debug> Clone for WeakSharedContainer<T> {
71    fn clone(&self) -> Self {
72        // Same implementation for both platforms, but different underlying types
73        WeakSharedContainer {
74            inner: self.inner.clone(),
75        }
76    }
77}
78
79impl<T: Debug + PartialEq> PartialEq for SharedContainer<T> {
80    fn eq(&self, other: &Self) -> bool {
81        match (self.read(), other.read()) {
82            (Some(self_val), Some(other_val)) => *self_val == *other_val,
83            _ => false,
84        }
85    }
86}
87
88impl<T: Debug> Clone for SharedContainer<T> {
89    fn clone(&self) -> Self {
90        #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
91        {
92            SharedContainer {
93                inner: Arc::clone(&self.inner),
94            }
95        }
96
97        #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
98        {
99            SharedContainer {
100                inner: Rc::clone(&self.inner),
101            }
102        }
103    }
104}
105
106impl<T: Debug + Clone> SharedContainer<T> {
107    /// Gets a clone of the contained value.
108    ///
109    /// This method acquires a read lock, clones the value, and releases the lock.
110    ///
111    /// # Returns
112    /// * `Some(T)`: A clone of the contained value
113    /// * `None`: If the lock couldn't be acquired
114    pub fn get_cloned(&self) -> Option<T> {
115        let guard = self.read()?;
116        Some((*guard).clone())
117    }
118}
119
120impl<T: Debug> SharedContainer<T> {
121    /// Creates a new `SharedContainer` containing the given value.
122    ///
123    /// # Parameters
124    /// * `value`: The value to store in the container
125    ///
126    /// # Returns
127    /// A new `SharedContainer` instance containing the value
128    pub fn new(value: T) -> Self {
129        #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
130        {
131            SharedContainer {
132                inner: Arc::new(RwLock::new(value)),
133            }
134        }
135
136        #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
137        {
138            SharedContainer {
139                inner: Rc::new(RefCell::new(value)),
140            }
141        }
142    }
143
144    /// Gets a read-only access guard to the contained value.
145    ///
146    /// # Returns
147    /// * `Some(SharedReadGuard<T>)`: A guard allowing read-only access to the value
148    /// * `None`: If the lock couldn't be acquired (in multi-threaded mode)
149    pub fn read(&self) -> Option<SharedReadGuard<T>> {
150        #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
151        {
152            match self.inner.read() {
153                Ok(guard) => Some(SharedReadGuard::Multi(guard)),
154                Err(_) => None,
155            }
156        }
157
158        #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
159        {
160            match self.inner.try_borrow() {
161                Ok(borrow) => Some(SharedReadGuard::Single(borrow)),
162                Err(_) => None,
163            }
164        }
165    }
166
167    /// Gets a writable access guard to the contained value.
168    ///
169    /// # Returns
170    /// * `Some(SharedWriteGuard<T>)`: A guard allowing read-write access to the value
171    /// * `None`: If the lock couldn't be acquired (in multi-threaded mode)
172    pub fn write(&self) -> Option<SharedWriteGuard<T>> {
173        #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
174        {
175            match self.inner.write() {
176                Ok(guard) => Some(SharedWriteGuard::Multi(guard)),
177                Err(_) => None,
178            }
179        }
180
181        #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
182        {
183            match self.inner.try_borrow_mut() {
184                Ok(borrow) => Some(SharedWriteGuard::Single(borrow)),
185                Err(_) => None,
186            }
187        }
188    }
189
190    /// Creates a weak reference to this container.
191    ///
192    /// A weak reference doesn't prevent the value from being dropped when no strong
193    /// references remain, which helps break reference cycles that could cause memory leaks.
194    ///
195    /// # Returns
196    /// A `WeakSharedContainer` that points to the same data
197    pub fn downgrade(&self) -> WeakSharedContainer<T> {
198        #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
199        {
200            WeakSharedContainer {
201                inner: Arc::downgrade(&self.inner),
202            }
203        }
204
205        #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
206        {
207            WeakSharedContainer {
208                inner: Rc::downgrade(&self.inner),
209            }
210        }
211    }
212}
213
214impl<T: Debug> WeakSharedContainer<T> {
215    /// Attempts to create a strong `SharedContainer` from this weak reference.
216    ///
217    /// This will succeed if the value has not yet been dropped, i.e., if there are
218    /// still other strong references to it.
219    ///
220    /// # Returns
221    /// * `Some(SharedContainer<T>)`: If the value still exists
222    /// * `None`: If the value has been dropped
223    pub fn upgrade(&self) -> Option<SharedContainer<T>> {
224        // Code is the same for both platforms, but types are different
225        self.inner.upgrade().map(|inner| SharedContainer { inner })
226    }
227}
228/// A read-only guard for accessing data in a `SharedContainer`.
229///
230/// This type abstracts over the differences between `RwLockReadGuard` (used in multi-threaded environments)
231/// and `Ref` (used in single-threaded environments like WebAssembly).
232///
233/// It implements `Deref` to allow transparent access to the underlying data.
234pub enum SharedReadGuard<'a, T: Debug> {
235    #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
236    Multi(RwLockReadGuard<'a, T>),
237
238    #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
239    Single(Ref<'a, T>),
240}
241
242impl<'a, T: Debug> Deref for SharedReadGuard<'a, T> {
243    type Target = T;
244
245    fn deref(&self) -> &Self::Target {
246        #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
247        {
248            match self {
249                SharedReadGuard::Multi(guard) => guard.deref(),
250            }
251        }
252
253        #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
254        {
255            match self {
256                SharedReadGuard::Single(borrow) => borrow.deref(),
257            }
258        }
259    }
260}
261
262/// A writable guard for accessing and modifying data in a `SharedContainer`.
263///
264/// This type abstracts over the differences between `RwLockWriteGuard` (used in multi-threaded environments)
265/// and `RefMut` (used in single-threaded environments like WebAssembly).
266///
267/// It implements both `Deref` and `DerefMut` to allow transparent access to the underlying data.
268pub enum SharedWriteGuard<'a, T: Debug> {
269    #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
270    Multi(RwLockWriteGuard<'a, T>),
271
272    #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
273    Single(RefMut<'a, T>),
274}
275
276impl<'a, T: Debug> Deref for SharedWriteGuard<'a, T> {
277    type Target = T;
278
279    fn deref(&self) -> &Self::Target {
280        #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
281        {
282            match self {
283                SharedWriteGuard::Multi(guard) => guard.deref(),
284            }
285        }
286
287        #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
288        {
289            match self {
290                SharedWriteGuard::Single(borrow) => borrow.deref(),
291            }
292        }
293    }
294}
295
296impl<'a, T: Debug> DerefMut for SharedWriteGuard<'a, T> {
297    fn deref_mut(&mut self) -> &mut Self::Target {
298        #[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm-impl")))]
299        {
300            match self {
301                SharedWriteGuard::Multi(guard) => guard.deref_mut(),
302            }
303        }
304
305        #[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
306        {
307            match self {
308                SharedWriteGuard::Single(borrow) => borrow.deref_mut(),
309            }
310        }
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317
318    #[derive(Debug, Clone, PartialEq)]
319    struct TestStruct {
320        value: i32,
321    }
322
323    #[test]
324    fn test_read_access() {
325        let container = SharedContainer::new(TestStruct { value: 42 });
326
327        // Read access
328        let guard = container.read().unwrap();
329        assert_eq!(guard.value, 42);
330    }
331
332    #[test]
333    fn test_write_access() {
334        let container = SharedContainer::new(TestStruct { value: 42 });
335
336        // Write access
337        {
338            let mut guard = container.write().unwrap();
339            guard.value = 100;
340        }
341
342        // Verify change
343        let guard = container.read().unwrap();
344        assert_eq!(guard.value, 100);
345    }
346
347    #[test]
348    fn test_clone_container() {
349        let container1 = SharedContainer::new(TestStruct { value: 42 });
350        let container2 = container1.clone();
351
352        // Modify through container2
353        {
354            let mut guard = container2.write().unwrap();
355            guard.value = 100;
356        }
357
358        // Verify change visible through container1
359        let guard = container1.read().unwrap();
360        assert_eq!(guard.value, 100);
361    }
362
363    #[test]
364    fn test_get_cloned() {
365        let container = SharedContainer::new(TestStruct { value: 42 });
366        let cloned = container.get_cloned().unwrap();
367        assert_eq!(cloned, TestStruct { value: 42 });
368
369        // Modify original
370        {
371            let mut guard = container.write().unwrap();
372            guard.value = 100;
373        }
374
375        // Cloned value should not change
376        assert_eq!(cloned, TestStruct { value: 42 });
377
378        // And we can get a new clone with updated value
379        let new_clone = container.get_cloned().unwrap();
380        assert_eq!(new_clone, TestStruct { value: 100 });
381    }
382
383    #[test]
384    fn test_weak_ref() {
385        let container = SharedContainer::new(TestStruct { value: 42 });
386
387        // Create a weak reference
388        let weak = container.downgrade();
389
390        // Weak reference can be upgraded to a strong reference
391        let container2 = weak.upgrade().unwrap();
392
393        // Both containers point to the same data
394        {
395            let mut guard = container2.write().unwrap();
396            guard.value = 100;
397        }
398
399        // Change visible through first container
400        {
401            let guard = container.read().unwrap();
402            assert_eq!(guard.value, 100);
403        }
404        // Drop all strong references
405        drop(container);
406        drop(container2);
407
408        // Weak reference can no longer be upgraded
409        assert!(weak.upgrade().is_none());
410    }
411
412    #[test]
413    fn test_weak_clone() {
414        let container = SharedContainer::new(TestStruct { value: 42 });
415
416        // Create a weak reference and clone it
417        let weak1 = container.downgrade();
418        let weak2 = weak1.clone();
419
420        // Both weak references can be upgraded
421        let container1 = weak1.upgrade().unwrap();
422        let container2 = weak2.upgrade().unwrap();
423
424        // Modify through container2
425        {
426            let mut guard = container2.write().unwrap();
427            guard.value = 100;
428        }
429
430        // Change visible through container1
431        {
432            let guard = container1.read().unwrap();
433            assert_eq!(guard.value, 100);
434        }
435
436        // Drop all strong references
437        drop(container);
438        drop(container1);
439        drop(container2);
440
441        // Neither weak reference can be upgraded
442        assert!(weak1.upgrade().is_none());
443        assert!(weak2.upgrade().is_none());
444    }
445}
446
447// Tests specifically for the WebAssembly implementation
448// These tests can be run on any platform by enabling the force-wasm-impl feature
449#[cfg(test)]
450#[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
451mod wasm_tests {
452    use super::*;
453
454    #[derive(Debug, Clone, PartialEq)]
455    struct TestStruct {
456        value: i32,
457    }
458
459    #[test]
460    fn test_wasm_read_access() {
461        let container = SharedContainer::new(TestStruct { value: 42 });
462
463        // Read access
464        let guard = container.read().unwrap();
465        assert_eq!(guard.value, 42);
466    }
467
468    #[test]
469    fn test_wasm_write_access() {
470        let container = SharedContainer::new(TestStruct { value: 42 });
471
472        // Write access
473        {
474            let mut guard = container.write().unwrap();
475            guard.value = 100;
476        }
477
478        // Verify change
479        let guard = container.read().unwrap();
480        assert_eq!(guard.value, 100);
481    }
482
483    #[test]
484    fn test_wasm_borrow_conflict() {
485        let container = SharedContainer::new(TestStruct { value: 42 });
486
487        // Get a read borrow
488        let _guard = container.read().unwrap();
489
490        // Trying to get a write borrow while a read borrow exists should fail
491        assert!(container.write().is_none());
492    }
493
494    #[test]
495    fn test_wasm_multiple_reads() {
496        let container = SharedContainer::new(TestStruct { value: 42 });
497
498        // Multiple read borrows should work
499        let _guard1 = container.read().unwrap();
500        let guard2 = container.read().unwrap();
501
502        assert_eq!(guard2.value, 42);
503    }
504
505    #[test]
506    fn test_wasm_weak_ref() {
507        let container = SharedContainer::new(TestStruct { value: 42 });
508        let weak = container.downgrade();
509
510        // Upgrade should work
511        let container2 = weak.upgrade().unwrap();
512        assert_eq!(container2.read().unwrap().value, 42);
513
514        // After dropping all strong references, upgrade should fail
515        drop(container);
516        drop(container2);
517        assert!(weak.upgrade().is_none());
518    }
519}