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:
7//! - `Arc<std::sync::RwLock<T>>` (used in standard multi-threaded environments)
8//! - `Arc<tokio::sync::RwLock<T>>` (used for async/await support)
9//! - `Rc<RefCell<T>>` (used in single-threaded environments like WebAssembly)
10//!
11//! This allows code using these containers to be written once but work efficiently
12//! in different contexts.
13//!
14//! ## Feature Flags
15//!
16//! This library provides several feature flags to customize its behavior:
17//!
18//! - **std-sync** (default): Uses `Arc<std::sync::RwLock<T>>` for thread-safe access
19//! - **tokio-sync**: Uses `Arc<tokio::sync::RwLock<T>>` for async/await support
20//! - **wasm-sync**: Uses `Rc<RefCell<T>>` for single-threaded environments
21//! - **force-wasm-impl**: Legacy feature, equivalent to wasm-sync
22//!
23//! ## Async Support
24//!
25//! When the `tokio-sync` feature is enabled, the library provides async versions of the read and write methods:
26//!
27//! ```rust
28//! # #[cfg(feature = "tokio-sync")]
29//! # async fn example() {
30//! # use shared_container::SharedContainer;
31//! let container = SharedContainer::new(42);
32//!
33//! // Read access
34//! let guard = container.read_async().await;
35//! println!("Value: {}", *guard);
36//!
37//! // Write access
38//! let mut guard = container.write_async().await;
39//! *guard = 100;
40//! # }
41//! ```
42//!
43//! Note that when using the `tokio-sync` feature, the synchronous `read()` and `write()` methods
44//! will always return `None`. You should use the async methods instead.
45
46use std::ops::{Deref, DerefMut};
47
48// Standard library synchronization primitives (default)
49#[cfg(all(
50    feature = "std-sync",
51    not(feature = "tokio-sync"),
52    not(feature = "wasm-sync")
53))]
54use std::sync::{Arc, RwLock, Weak};
55
56// Tokio async synchronization primitives
57#[cfg(feature = "tokio-sync")]
58use std::sync::{Arc, Weak};
59#[cfg(feature = "tokio-sync")]
60use tokio::sync::RwLock;
61
62// WebAssembly/single-threaded synchronization primitives
63#[cfg(any(
64    feature = "wasm-sync",
65    all(
66        target_arch = "wasm32",
67        not(feature = "std-sync"),
68        not(feature = "tokio-sync")
69    )
70))]
71use std::cell::{Ref, RefCell, RefMut};
72#[cfg(any(
73    feature = "wasm-sync",
74    all(
75        target_arch = "wasm32",
76        not(feature = "std-sync"),
77        not(feature = "tokio-sync")
78    )
79))]
80use std::rc::{Rc, Weak};
81
82/// A unified container for shared data that works in both multi-threaded and single-threaded environments.
83///
84/// This struct provides an abstraction over different container types:
85/// - `Arc<std::sync::RwLock<T>>` (used in standard multi-threaded environments)
86/// - `Arc<tokio::sync::RwLock<T>>` (used for async/await support)
87/// - `Rc<RefCell<T>>` (used in single-threaded environments like WebAssembly)
88///
89/// It allows code to be written once but compile to the most efficient implementation
90/// based on the environment where it will run and the features enabled.
91#[derive(Debug)]
92pub struct SharedContainer<T> {
93    // Standard library thread-safe implementation
94    #[cfg(all(
95        feature = "std-sync",
96        not(feature = "tokio-sync"),
97        not(feature = "wasm-sync")
98    ))]
99    inner: Arc<RwLock<T>>,
100
101    // Tokio async implementation
102    #[cfg(feature = "tokio-sync")]
103    inner: Arc<RwLock<T>>,
104
105    // Single-threaded implementation for WebAssembly
106    #[cfg(any(
107        feature = "wasm-sync",
108        all(
109            target_arch = "wasm32",
110            not(feature = "std-sync"),
111            not(feature = "tokio-sync")
112        )
113    ))]
114    inner: Rc<RefCell<T>>,
115}
116
117// Implement Send and Sync for SharedContainer only for thread-safe implementations
118#[cfg(any(feature = "std-sync", feature = "tokio-sync"))]
119unsafe impl<T: Send> Send for SharedContainer<T> {}
120
121#[cfg(any(feature = "std-sync", feature = "tokio-sync"))]
122unsafe impl<T: Send + Sync> Sync for SharedContainer<T> {}
123
124/// A weak reference to a `SharedContainer`.
125///
126/// This struct provides an abstraction over different weak reference types:
127/// - `Weak<std::sync::RwLock<T>>` (used in standard multi-threaded environments)
128/// - `Weak<tokio::sync::RwLock<T>>` (used for async/await support)
129/// - `Weak<RefCell<T>>` (used in single-threaded environments like WebAssembly)
130///
131/// Weak references don't prevent the value from being dropped when no strong references
132/// remain. This helps break reference cycles that could cause memory leaks.
133#[derive(Debug)]
134pub struct WeakSharedContainer<T> {
135    // Standard library thread-safe implementation
136    #[cfg(all(
137        feature = "std-sync",
138        not(feature = "tokio-sync"),
139        not(feature = "wasm-sync")
140    ))]
141    inner: Weak<RwLock<T>>,
142
143    // Tokio async implementation
144    #[cfg(feature = "tokio-sync")]
145    inner: Weak<RwLock<T>>,
146
147    // Single-threaded implementation for WebAssembly
148    #[cfg(any(
149        feature = "wasm-sync",
150        all(
151            target_arch = "wasm32",
152            not(feature = "std-sync"),
153            not(feature = "tokio-sync")
154        )
155    ))]
156    inner: Weak<RefCell<T>>,
157}
158
159impl<T> Clone for WeakSharedContainer<T> {
160    fn clone(&self) -> Self {
161        // Same implementation for both platforms, but different underlying types
162        WeakSharedContainer {
163            inner: self.inner.clone(),
164        }
165    }
166}
167
168impl<T: PartialEq> PartialEq for SharedContainer<T> {
169    fn eq(&self, other: &Self) -> bool {
170        #[cfg(feature = "tokio-sync")]
171        {
172            // For tokio-sync, we need to block on the async read
173            use std::sync::Arc;
174            let self_inner = Arc::clone(&self.inner);
175            let other_inner = Arc::clone(&other.inner);
176
177            tokio::task::block_in_place(|| {
178                tokio::runtime::Handle::current().block_on(async {
179                    let self_val = self_inner.read().await;
180                    let other_val = other_inner.read().await;
181                    *self_val == *other_val
182                })
183            })
184        }
185
186        #[cfg(not(feature = "tokio-sync"))]
187        {
188            match (self.read(), other.read()) {
189                (Some(self_val), Some(other_val)) => *self_val == *other_val,
190                _ => false,
191            }
192        }
193    }
194}
195
196impl<T> Clone for SharedContainer<T> {
197    fn clone(&self) -> Self {
198        #[cfg(all(
199            feature = "std-sync",
200            not(feature = "tokio-sync"),
201            not(feature = "wasm-sync")
202        ))]
203        {
204            SharedContainer {
205                inner: Arc::clone(&self.inner),
206            }
207        }
208
209        #[cfg(feature = "tokio-sync")]
210        {
211            SharedContainer {
212                inner: Arc::clone(&self.inner),
213            }
214        }
215
216        #[cfg(any(
217            feature = "wasm-sync",
218            all(
219                target_arch = "wasm32",
220                not(feature = "std-sync"),
221                not(feature = "tokio-sync")
222            )
223        ))]
224        {
225            SharedContainer {
226                inner: Rc::clone(&self.inner),
227            }
228        }
229    }
230}
231
232impl<T: Clone> SharedContainer<T> {
233    /// Gets a clone of the contained value.
234    ///
235    /// This method acquires a read lock, clones the value, and releases the lock.
236    ///
237    /// # Returns
238    /// * `Some(T)`: A clone of the contained value
239    /// * `None`: If the lock couldn't be acquired
240    ///
241    /// # Note
242    /// When using the `tokio-sync` feature, this method will try to acquire the lock
243    /// in a blocking manner, which may not be ideal for async code. Consider using
244    /// `get_cloned_async()` instead.
245    pub fn get_cloned(&self) -> Option<T> {
246        #[cfg(feature = "tokio-sync")]
247        {
248            // For tokio-sync, we need to block on the async read
249            // This is not ideal for async code, but it allows the method to work
250            // in both sync and async contexts
251            use std::sync::Arc;
252            let inner = Arc::clone(&self.inner);
253            let value = tokio::task::block_in_place(|| {
254                tokio::runtime::Handle::current().block_on(async {
255                    let guard = inner.read().await;
256                    (*guard).clone()
257                })
258            });
259            Some(value)
260        }
261
262        #[cfg(not(feature = "tokio-sync"))]
263        {
264            let guard = self.read()?;
265            Some((*guard).clone())
266        }
267    }
268
269    /// Gets a clone of the contained value asynchronously.
270    ///
271    /// This method is only available when the `tokio-sync` feature is enabled.
272    ///
273    /// # Returns
274    /// A clone of the contained value
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// # #[cfg(feature = "tokio-sync")]
280    /// # async fn example() {
281    /// # use shared_container::SharedContainer;
282    /// let container = SharedContainer::new(42);
283    /// let value = container.get_cloned_async().await;
284    /// assert_eq!(value, 42);
285    /// # }
286    /// ```
287    #[cfg(feature = "tokio-sync")]
288    pub async fn get_cloned_async(&self) -> T
289    where
290        T: Clone,
291    {
292        let guard = self.inner.read().await;
293        (*guard).clone()
294    }
295}
296
297impl<T> SharedContainer<T> {
298    /// Creates a new `SharedContainer` containing the given value.
299    ///
300    /// # Parameters
301    /// * `value`: The value to store in the container
302    ///
303    /// # Returns
304    /// A new `SharedContainer` instance containing the value
305    pub fn new(value: T) -> Self {
306        #[cfg(all(
307            feature = "std-sync",
308            not(feature = "tokio-sync"),
309            not(feature = "wasm-sync")
310        ))]
311        {
312            SharedContainer {
313                inner: Arc::new(RwLock::new(value)),
314            }
315        }
316
317        #[cfg(feature = "tokio-sync")]
318        {
319            SharedContainer {
320                inner: Arc::new(RwLock::new(value)),
321            }
322        }
323
324        #[cfg(any(
325            feature = "wasm-sync",
326            all(
327                target_arch = "wasm32",
328                not(feature = "std-sync"),
329                not(feature = "tokio-sync")
330            )
331        ))]
332        {
333            SharedContainer {
334                inner: Rc::new(RefCell::new(value)),
335            }
336        }
337    }
338
339    /// Gets a read-only access guard to the contained value.
340    ///
341    /// # Returns
342    /// * `Some(SharedReadGuard<T>)`: A guard allowing read-only access to the value
343    /// * `None`: If the lock couldn't be acquired
344    ///
345    /// # Note
346    /// When using the `tokio-sync` feature, this method will always return `None`.
347    /// Use `read_async()` instead for async access.
348    pub fn read(&self) -> Option<SharedReadGuard<T>> {
349        #[cfg(all(
350            feature = "std-sync",
351            not(feature = "tokio-sync"),
352            not(feature = "wasm-sync")
353        ))]
354        {
355            match self.inner.read() {
356                Ok(guard) => Some(SharedReadGuard::StdSync(guard)),
357                Err(_) => None,
358            }
359        }
360
361        #[cfg(feature = "tokio-sync")]
362        {
363            // Tokio's RwLock doesn't have a non-async read method, so we can't use it here
364            // Users should use read_async instead
365            None
366        }
367
368        #[cfg(any(
369            feature = "wasm-sync",
370            all(
371                target_arch = "wasm32",
372                not(feature = "std-sync"),
373                not(feature = "tokio-sync")
374            )
375        ))]
376        {
377            match self.inner.try_borrow() {
378                Ok(borrow) => Some(SharedReadGuard::Single(borrow)),
379                Err(_) => None,
380            }
381        }
382    }
383
384    /// Gets a writable access guard to the contained value.
385    ///
386    /// # Returns
387    /// * `Some(SharedWriteGuard<T>)`: A guard allowing read-write access to the value
388    /// * `None`: If the lock couldn't be acquired
389    ///
390    /// # Note
391    /// When using the `tokio-sync` feature, this method will always return `None`.
392    /// Use `write_async()` instead for async access.
393    pub fn write(&self) -> Option<SharedWriteGuard<T>> {
394        #[cfg(all(
395            feature = "std-sync",
396            not(feature = "tokio-sync"),
397            not(feature = "wasm-sync")
398        ))]
399        {
400            match self.inner.write() {
401                Ok(guard) => Some(SharedWriteGuard::StdSync(guard)),
402                Err(_) => None,
403            }
404        }
405
406        #[cfg(feature = "tokio-sync")]
407        {
408            // Tokio's RwLock doesn't have a non-async write method, so we can't use it here
409            // Users should use write_async instead
410            None
411        }
412
413        #[cfg(any(
414            feature = "wasm-sync",
415            all(
416                target_arch = "wasm32",
417                not(feature = "std-sync"),
418                not(feature = "tokio-sync")
419            )
420        ))]
421        {
422            match self.inner.try_borrow_mut() {
423                Ok(borrow) => Some(SharedWriteGuard::Single(borrow)),
424                Err(_) => None,
425            }
426        }
427    }
428
429    /// Creates a weak reference to this container.
430    ///
431    /// A weak reference doesn't prevent the value from being dropped when no strong
432    /// references remain, which helps break reference cycles that could cause memory leaks.
433    ///
434    /// # Returns
435    /// A `WeakSharedContainer` that points to the same data
436    pub fn downgrade(&self) -> WeakSharedContainer<T> {
437        #[cfg(all(
438            feature = "std-sync",
439            not(feature = "tokio-sync"),
440            not(feature = "wasm-sync")
441        ))]
442        {
443            WeakSharedContainer {
444                inner: Arc::downgrade(&self.inner),
445            }
446        }
447
448        #[cfg(feature = "tokio-sync")]
449        {
450            WeakSharedContainer {
451                inner: Arc::downgrade(&self.inner),
452            }
453        }
454
455        #[cfg(any(
456            feature = "wasm-sync",
457            all(
458                target_arch = "wasm32",
459                not(feature = "std-sync"),
460                not(feature = "tokio-sync")
461            )
462        ))]
463        {
464            WeakSharedContainer {
465                inner: Rc::downgrade(&self.inner),
466            }
467        }
468    }
469
470    /// Asynchronously gets a read-only access guard to the contained value.
471    ///
472    /// This method is only available when the `tokio-sync` feature is enabled.
473    ///
474    /// # Returns
475    /// A guard allowing read-only access to the value
476    ///
477    /// # Examples
478    ///
479    /// ```
480    /// # #[cfg(feature = "tokio-sync")]
481    /// # async fn example() {
482    /// # use shared_container::SharedContainer;
483    /// let container = SharedContainer::new(42);
484    /// let guard = container.read_async().await;
485    /// assert_eq!(*guard, 42);
486    /// # }
487    /// ```
488    #[cfg(feature = "tokio-sync")]
489    pub async fn read_async(&self) -> SharedReadGuard<'_, T> {
490        let guard = self.inner.read().await;
491        SharedReadGuard::TokioSync(guard)
492    }
493
494    /// Asynchronously gets a writable access guard to the contained value.
495    ///
496    /// This method is only available when the `tokio-sync` feature is enabled.
497    ///
498    /// # Returns
499    /// A guard allowing read-write access to the value
500    ///
501    /// # Examples
502    ///
503    /// ```
504    /// # #[cfg(feature = "tokio-sync")]
505    /// # async fn example() {
506    /// # use shared_container::SharedContainer;
507    /// let container = SharedContainer::new(42);
508    /// let mut guard = container.write_async().await;
509    /// *guard = 100;
510    /// # }
511    /// ```
512    #[cfg(feature = "tokio-sync")]
513    pub async fn write_async(&self) -> SharedWriteGuard<'_, T> {
514        let guard = self.inner.write().await;
515        SharedWriteGuard::TokioSync(guard)
516    }
517}
518
519impl<T> WeakSharedContainer<T> {
520    /// Attempts to create a strong `SharedContainer` from this weak reference.
521    ///
522    /// This will succeed if the value has not yet been dropped, i.e., if there are
523    /// still other strong references to it.
524    ///
525    /// # Returns
526    /// * `Some(SharedContainer<T>)`: If the value still exists
527    /// * `None`: If the value has been dropped
528    pub fn upgrade(&self) -> Option<SharedContainer<T>> {
529        // Code is the same for both platforms, but types are different
530        self.inner.upgrade().map(|inner| SharedContainer { inner })
531    }
532}
533/// A read-only guard for accessing data in a `SharedContainer`.
534///
535/// This type abstracts over the differences between different read guards:
536/// - `std::sync::RwLockReadGuard` (used in standard multi-threaded environments)
537/// - `tokio::sync::RwLockReadGuard` (used for async/await support)
538/// - `std::cell::Ref` (used in single-threaded environments like WebAssembly)
539///
540/// It implements `Deref` to allow transparent access to the underlying data.
541pub enum SharedReadGuard<'a, T> {
542    #[cfg(all(
543        feature = "std-sync",
544        not(feature = "tokio-sync"),
545        not(feature = "wasm-sync")
546    ))]
547    StdSync(std::sync::RwLockReadGuard<'a, T>),
548
549    #[cfg(feature = "tokio-sync")]
550    TokioSync(tokio::sync::RwLockReadGuard<'a, T>),
551
552    #[cfg(any(
553        feature = "wasm-sync",
554        all(
555            target_arch = "wasm32",
556            not(feature = "std-sync"),
557            not(feature = "tokio-sync")
558        )
559    ))]
560    Single(Ref<'a, T>),
561}
562
563impl<'a, T> Deref for SharedReadGuard<'a, T> {
564    type Target = T;
565
566    fn deref(&self) -> &Self::Target {
567        #[cfg(all(
568            feature = "std-sync",
569            not(feature = "tokio-sync"),
570            not(feature = "wasm-sync")
571        ))]
572        {
573            match self {
574                SharedReadGuard::StdSync(guard) => guard.deref(),
575                #[allow(unreachable_patterns)]
576                _ => unreachable!(),
577            }
578        }
579
580        #[cfg(feature = "tokio-sync")]
581        {
582            match self {
583                SharedReadGuard::TokioSync(guard) => guard.deref(),
584                #[allow(unreachable_patterns)]
585                _ => unreachable!(),
586            }
587        }
588
589        #[cfg(any(
590            feature = "wasm-sync",
591            all(
592                target_arch = "wasm32",
593                not(feature = "std-sync"),
594                not(feature = "tokio-sync")
595            )
596        ))]
597        {
598            match self {
599                SharedReadGuard::Single(borrow) => borrow.deref(),
600                #[allow(unreachable_patterns)]
601                _ => unreachable!(),
602            }
603        }
604    }
605}
606
607/// A writable guard for accessing and modifying data in a `SharedContainer`.
608///
609/// This type abstracts over the differences between different write guards:
610/// - `std::sync::RwLockWriteGuard` (used in standard multi-threaded environments)
611/// - `tokio::sync::RwLockWriteGuard` (used for async/await support)
612/// - `std::cell::RefMut` (used in single-threaded environments like WebAssembly)
613///
614/// It implements both `Deref` and `DerefMut` to allow transparent access to the underlying data.
615pub enum SharedWriteGuard<'a, T> {
616    #[cfg(all(
617        feature = "std-sync",
618        not(feature = "tokio-sync"),
619        not(feature = "wasm-sync")
620    ))]
621    StdSync(std::sync::RwLockWriteGuard<'a, T>),
622
623    #[cfg(feature = "tokio-sync")]
624    TokioSync(tokio::sync::RwLockWriteGuard<'a, T>),
625
626    #[cfg(any(
627        feature = "wasm-sync",
628        all(
629            target_arch = "wasm32",
630            not(feature = "std-sync"),
631            not(feature = "tokio-sync")
632        )
633    ))]
634    Single(RefMut<'a, T>),
635}
636
637impl<'a, T> Deref for SharedWriteGuard<'a, T> {
638    type Target = T;
639
640    fn deref(&self) -> &Self::Target {
641        #[cfg(all(
642            feature = "std-sync",
643            not(feature = "tokio-sync"),
644            not(feature = "wasm-sync")
645        ))]
646        {
647            match self {
648                SharedWriteGuard::StdSync(guard) => guard.deref(),
649                #[allow(unreachable_patterns)]
650                _ => unreachable!(),
651            }
652        }
653
654        #[cfg(feature = "tokio-sync")]
655        {
656            match self {
657                SharedWriteGuard::TokioSync(guard) => guard.deref(),
658                #[allow(unreachable_patterns)]
659                _ => unreachable!(),
660            }
661        }
662
663        #[cfg(any(
664            feature = "wasm-sync",
665            all(
666                target_arch = "wasm32",
667                not(feature = "std-sync"),
668                not(feature = "tokio-sync")
669            )
670        ))]
671        {
672            match self {
673                SharedWriteGuard::Single(borrow) => borrow.deref(),
674                #[allow(unreachable_patterns)]
675                _ => unreachable!(),
676            }
677        }
678    }
679}
680
681impl<'a, T> DerefMut for SharedWriteGuard<'a, T> {
682    fn deref_mut(&mut self) -> &mut Self::Target {
683        #[cfg(all(
684            feature = "std-sync",
685            not(feature = "tokio-sync"),
686            not(feature = "wasm-sync")
687        ))]
688        {
689            match self {
690                SharedWriteGuard::StdSync(guard) => guard.deref_mut(),
691                #[allow(unreachable_patterns)]
692                _ => unreachable!(),
693            }
694        }
695
696        #[cfg(feature = "tokio-sync")]
697        {
698            match self {
699                SharedWriteGuard::TokioSync(guard) => guard.deref_mut(),
700                #[allow(unreachable_patterns)]
701                _ => unreachable!(),
702            }
703        }
704
705        #[cfg(any(
706            feature = "wasm-sync",
707            all(
708                target_arch = "wasm32",
709                not(feature = "std-sync"),
710                not(feature = "tokio-sync")
711            )
712        ))]
713        {
714            match self {
715                SharedWriteGuard::Single(borrow) => borrow.deref_mut(),
716                #[allow(unreachable_patterns)]
717                _ => unreachable!(),
718            }
719        }
720    }
721}
722
723#[cfg(test)]
724mod tests {
725
726    #[derive(Debug, Clone, PartialEq)]
727    #[allow(dead_code)]
728    struct TestStruct {
729        value: i32,
730    }
731
732    // Skip synchronous tests when tokio-sync is enabled
733    #[cfg(not(feature = "tokio-sync"))]
734    mod sync_tests {
735        use super::*;
736        use crate::SharedContainer;
737
738        #[test]
739        fn test_read_access() {
740            let container = SharedContainer::new(TestStruct { value: 42 });
741
742            // Read access
743            let guard = container.read().unwrap();
744            assert_eq!(guard.value, 42);
745        }
746
747        #[test]
748        fn test_write_access() {
749            let container = SharedContainer::new(TestStruct { value: 42 });
750
751            // Write access
752            {
753                let mut guard = container.write().unwrap();
754                guard.value = 100;
755            }
756
757            // Verify change
758            let guard = container.read().unwrap();
759            assert_eq!(guard.value, 100);
760        }
761
762        #[test]
763        fn test_clone_container() {
764            let container1 = SharedContainer::new(TestStruct { value: 42 });
765            let container2 = container1.clone();
766
767            // Modify through container2
768            {
769                let mut guard = container2.write().unwrap();
770                guard.value = 100;
771            }
772
773            // Verify change visible through container1
774            let guard = container1.read().unwrap();
775            assert_eq!(guard.value, 100);
776        }
777
778        #[test]
779        fn test_get_cloned() {
780            let container = SharedContainer::new(TestStruct { value: 42 });
781            let cloned = container.get_cloned().unwrap();
782            assert_eq!(cloned, TestStruct { value: 42 });
783
784            // Modify original
785            {
786                let mut guard = container.write().unwrap();
787                guard.value = 100;
788            }
789
790            // Cloned value should not change
791            assert_eq!(cloned, TestStruct { value: 42 });
792
793            // And we can get a new clone with updated value
794            let new_clone = container.get_cloned().unwrap();
795            assert_eq!(new_clone, TestStruct { value: 100 });
796        }
797
798        #[test]
799        fn test_weak_ref() {
800            let container = SharedContainer::new(TestStruct { value: 42 });
801
802            // Create a weak reference
803            let weak = container.downgrade();
804
805            // Weak reference can be upgraded to a strong reference
806            let container2 = weak.upgrade().unwrap();
807
808            // Both containers point to the same data
809            {
810                let mut guard = container2.write().unwrap();
811                guard.value = 100;
812            }
813
814            // Change visible through first container
815            {
816                let guard = container.read().unwrap();
817                assert_eq!(guard.value, 100);
818            }
819            // Drop all strong references
820            drop(container);
821            drop(container2);
822
823            // Weak reference can no longer be upgraded
824            assert!(weak.upgrade().is_none());
825        }
826
827        #[test]
828        fn test_weak_clone() {
829            let container = SharedContainer::new(TestStruct { value: 42 });
830
831            // Create a weak reference and clone it
832            let weak1 = container.downgrade();
833            let weak2 = weak1.clone();
834
835            // Both weak references can be upgraded
836            let container1 = weak1.upgrade().unwrap();
837            let container2 = weak2.upgrade().unwrap();
838
839            // Modify through container2
840            {
841                let mut guard = container2.write().unwrap();
842                guard.value = 100;
843            }
844
845            // Change visible through container1
846            {
847                let guard = container1.read().unwrap();
848                assert_eq!(guard.value, 100);
849            }
850
851            // Drop all strong references
852            drop(container);
853            drop(container1);
854            drop(container2);
855
856            // Neither weak reference can be upgraded
857            assert!(weak1.upgrade().is_none());
858            assert!(weak2.upgrade().is_none());
859        }
860    }
861}
862
863// Tests specifically for the tokio async implementation
864#[cfg(test)]
865#[cfg(feature = "tokio-sync")]
866mod tokio_tests {
867    use super::*;
868    use tokio::runtime::Runtime;
869
870    #[derive(Debug, Clone, PartialEq)]
871    struct TestStruct {
872        value: i32,
873    }
874
875    #[test]
876    fn test_tokio_read_access() {
877        let rt = Runtime::new().unwrap();
878        rt.block_on(async {
879            let container = SharedContainer::new(TestStruct { value: 42 });
880
881            // Synchronous read should return None with tokio-sync
882            assert!(container.read().is_none());
883
884            // Async read access
885            let guard = container.read_async().await;
886            assert_eq!(guard.value, 42);
887        });
888    }
889
890    #[test]
891    fn test_tokio_write_access() {
892        let rt = Runtime::new().unwrap();
893        rt.block_on(async {
894            let container = SharedContainer::new(TestStruct { value: 42 });
895
896            // Synchronous write should return None with tokio-sync
897            assert!(container.write().is_none());
898
899            // Async write access
900            {
901                let mut guard = container.write_async().await;
902                guard.value = 100;
903            }
904
905            // Verify change
906            let guard = container.read_async().await;
907            assert_eq!(guard.value, 100);
908        });
909    }
910
911    #[test]
912    fn test_tokio_clone_container() {
913        let rt = Runtime::new().unwrap();
914        rt.block_on(async {
915            let container1 = SharedContainer::new(TestStruct { value: 42 });
916            let container2 = container1.clone();
917
918            // Modify through container2
919            {
920                let mut guard = container2.write_async().await;
921                guard.value = 100;
922            }
923
924            // Verify change visible through container1
925            let guard = container1.read_async().await;
926            assert_eq!(guard.value, 100);
927        });
928    }
929
930    #[test]
931    fn test_tokio_weak_ref() {
932        let rt = Runtime::new().unwrap();
933        rt.block_on(async {
934            let container = SharedContainer::new(TestStruct { value: 42 });
935
936            // Create a weak reference
937            let weak = container.downgrade();
938
939            // Weak reference can be upgraded to a strong reference
940            let container2 = weak.upgrade().unwrap();
941
942            // Both containers point to the same data
943            {
944                let mut guard = container2.write_async().await;
945                guard.value = 100;
946            }
947
948            // Change visible through first container
949            {
950                let guard = container.read_async().await;
951                assert_eq!(guard.value, 100);
952            }
953
954            // Drop all strong references
955            drop(container);
956            drop(container2);
957
958            // Weak reference can no longer be upgraded
959            assert!(weak.upgrade().is_none());
960        });
961    }
962}
963
964// Tests specifically for the WebAssembly implementation
965// These tests can be run on any platform by enabling the force-wasm-impl feature
966#[cfg(test)]
967#[cfg(any(target_arch = "wasm32", feature = "force-wasm-impl"))]
968mod wasm_tests {
969    use super::*;
970
971    #[derive(Debug, Clone, PartialEq)]
972    struct TestStruct {
973        value: i32,
974    }
975
976    #[test]
977    fn test_wasm_read_access() {
978        let container = SharedContainer::new(TestStruct { value: 42 });
979
980        // Read access
981        let guard = container.read().unwrap();
982        assert_eq!(guard.value, 42);
983    }
984
985    #[test]
986    fn test_wasm_write_access() {
987        let container = SharedContainer::new(TestStruct { value: 42 });
988
989        // Write access
990        {
991            let mut guard = container.write().unwrap();
992            guard.value = 100;
993        }
994
995        // Verify change
996        let guard = container.read().unwrap();
997        assert_eq!(guard.value, 100);
998    }
999
1000    #[test]
1001    fn test_wasm_borrow_conflict() {
1002        let container = SharedContainer::new(TestStruct { value: 42 });
1003
1004        // Get a read borrow
1005        let _guard = container.read().unwrap();
1006
1007        // Trying to get a write borrow while a read borrow exists should fail
1008        assert!(container.write().is_none());
1009    }
1010
1011    #[test]
1012    fn test_wasm_multiple_reads() {
1013        let container = SharedContainer::new(TestStruct { value: 42 });
1014
1015        // Multiple read borrows should work
1016        let _guard1 = container.read().unwrap();
1017        let guard2 = container.read().unwrap();
1018
1019        assert_eq!(guard2.value, 42);
1020    }
1021
1022    #[test]
1023    fn test_wasm_weak_ref() {
1024        let container = SharedContainer::new(TestStruct { value: 42 });
1025        let weak = container.downgrade();
1026
1027        // Upgrade should work
1028        let container2 = weak.upgrade().unwrap();
1029        assert_eq!(container2.read().unwrap().value, 42);
1030
1031        // After dropping all strong references, upgrade should fail
1032        drop(container);
1033        drop(container2);
1034        assert!(weak.upgrade().is_none());
1035    }
1036}