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}