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