diff --git a/src/acquirable.rs b/src/acquirable.rs index c2335a1..ca28708 100644 --- a/src/acquirable.rs +++ b/src/acquirable.rs @@ -13,6 +13,106 @@ use crate::{ raw_mutex::RawMutex, }; +/// Uniform access to a [`Mutex`] through any wrapper type. +/// +/// Implemented for bare references, `Arc`, `Rc`, and `Box`. Used by +/// tuple [`Acquirable`] impls so that `LockSet::new((&arc, &bare))` +/// and `key.lock_with(&(&arc, &rc), ...)` work transparently. +/// +/// Associated types (`Data`, `Lvl`, `RawMtx`) are determined by +/// `Self`, avoiding unconstrained type parameter issues in generic +/// tuple impls. +pub trait MutexRef<'a> { + /// The data type guarded by the mutex. + type Data: 'a; + + /// The level of the mutex. + type Lvl: IsLevel; + + /// The raw mutex backend. + type RawMtx: RawMutex + 'a; + + /// Return this mutex's [`LockId`]. + fn id(&self) -> LockId; + + /// Lock the mutex and return the guard. + fn lock_ref(&'a self) -> MutexGuard<'a, Self::RawMtx, Self::Data>; +} + +impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'a> for &'a Mutex { + type Data = T; + type Lvl = Lvl; + type RawMtx = R; + + fn id(&self) -> LockId { + Mutex::id(self) + } + + fn lock_ref(&'a self) -> MutexGuard<'a, R, T> { + MutexGuard { + data: &self.data, + _raw_guard: self.raw.lock(), + } + } +} + +impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'a> + for &'a alloc::sync::Arc> +{ + type Data = T; + type Lvl = Lvl; + type RawMtx = R; + + fn id(&self) -> LockId { + Mutex::id(self) + } + + fn lock_ref(&'a self) -> MutexGuard<'a, R, T> { + MutexGuard { + data: &self.data, + _raw_guard: self.raw.lock(), + } + } +} + +impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'a> + for &'a alloc::rc::Rc> +{ + type Data = T; + type Lvl = Lvl; + type RawMtx = R; + + fn id(&self) -> LockId { + Mutex::id(self) + } + + fn lock_ref(&'a self) -> MutexGuard<'a, R, T> { + MutexGuard { + data: &self.data, + _raw_guard: self.raw.lock(), + } + } +} + +impl<'a, T: 'a, Lvl: IsLevel, R: RawMutex + 'a> MutexRef<'a> + for &'a alloc::boxed::Box> +{ + type Data = T; + type Lvl = Lvl; + type RawMtx = R; + + fn id(&self) -> LockId { + Mutex::id(self) + } + + fn lock_ref(&'a self) -> MutexGuard<'a, R, T> { + MutexGuard { + data: &self.data, + _raw_guard: self.raw.lock(), + } + } +} + /// A type (or collection of types) that can be locked atomically. /// /// This trait is primarily internal bookkeeping -- most users diff --git a/src/acquirable/tuples.rs b/src/acquirable/tuples.rs index 9d34ae9..a694c9f 100644 --- a/src/acquirable/tuples.rs +++ b/src/acquirable/tuples.rs @@ -1,118 +1,113 @@ //! [`Acquirable`] implementations for tuples of mutex references. //! -//! Generated via macro for arities 2 through 12. Elements can be at -//! different levels -- `MinLvl` and `MaxLvl` are computed via the -//! [`MinLevel`] and -//! [`MaxLevel`] traits. +//! Generated via macro for arities 2 through 12. +//! +//! All tuple elements implement [`MutexRef`], so any combination of +//! bare references, `Arc`, `Rc`, and `Box` is accepted. +//! +//! The 2-tuple supports different levels per element (multi-level). +//! Arities 3--12 require the same level. use crate::{ - acquirable::Acquirable, - level::{IsLevel, MaxLevel, MinLevel}, - mutex::{Mutex, guard::MutexGuard}, - raw_mutex::RawMutex, + acquirable::{Acquirable, MutexRef}, + level::{MaxLevel, MinLevel}, + mutex::guard::MutexGuard, }; -/// Lock a single element by index, returning an Option-wrapped guard. -macro_rules! lock_if_match { - ($self:expr, $target:expr, $idx:tt) => { - if $target == $idx { - Some(MutexGuard { - data: &$self.$idx.data, - _raw_guard: $self.$idx.raw.lock(), - }) - } else { - None - } - }; -} - -// -- 2-tuple: explicit, no recursive macro needed -- +// -- 2-tuple: multi-level, different T per element -- -impl<'a, A: 'a, B: 'a, Lvl0: IsLevel, Lvl1: IsLevel, R: RawMutex + 'a> Acquirable<'a> - for (&'a Mutex, &'a Mutex) +impl<'a, A, B> Acquirable<'a> for (A, B) where - Lvl0: MinLevel + MaxLevel, + A: MutexRef<'a>, + B: MutexRef<'a, RawMtx = A::RawMtx>, + A::Lvl: MinLevel + MaxLevel, { - type MinLvl = >::Min; - type MaxLvl = >::Max; - type Guard = (MutexGuard<'a, R, A>, MutexGuard<'a, R, B>); + type MinLvl = >::Min; + type MaxLvl = >::Max; + type Guard = ( + MutexGuard<'a, A::RawMtx, A::Data>, + MutexGuard<'a, B::RawMtx, B::Data>, + ); fn collect_ids(&self, out: &mut alloc::vec::Vec) { out.push(self.0.id()); out.push(self.1.id()); } - #[allow(clippy::indexing_slicing, clippy::expect_used, non_snake_case)] + #[allow(clippy::expect_used, non_snake_case)] fn lock_sorted(&'a self, sorted_indices: &[usize]) -> Self::Guard { - let mut A: Option> = None; - let mut B: Option> = None; + let mut GA: Option> = None; + let mut GB: Option> = None; for &target in sorted_indices { - if let Some(g) = lock_if_match!(self, target, 0) { - A = Some(g); - continue; - } - if let Some(g) = lock_if_match!(self, target, 1) { - B = Some(g); - continue; + match target { + 0 => GA = Some(self.0.lock_ref()), + 1 => GB = Some(self.1.lock_ref()), + _ => unreachable!(), } - unreachable!(); } ( - A.expect("lock_sorted: missing index"), - B.expect("lock_sorted: missing index"), + GA.expect("lock_sorted: missing index 0"), + GB.expect("lock_sorted: missing index 1"), ) } } -// For higher arities, we keep same-level only (all elements share Lvl). -// Multi-level support for 3+ tuples can be added later with more complex -// macro machinery or manual impls. +// -- 3-12 tuples: same level, same backend, different T per element -- +// +// Each element implements MutexRef with associated types. The macro +// constrains all elements to share the same Lvl and RawMtx. macro_rules! impl_lockable_tuple_same_level { - ($n:literal: $($idx:tt $T:ident),+) => { - impl<'a, $($T: 'a,)+ Lvl: IsLevel, R: RawMutex + 'a> - Acquirable<'a> - for ($(&'a Mutex<$T, Lvl, R>,)+) + ($n:literal: $first_idx:tt $first_T:ident $first_G:ident $(, $idx:tt $T:ident $G:ident)+) => { + impl<'a, $first_G, $($G,)+> Acquirable<'a> + for ($first_G, $($G,)+) + where + $first_G: MutexRef<'a>, + $($G: MutexRef<'a, Lvl = $first_G::Lvl, RawMtx = $first_G::RawMtx>,)+ { - type MinLvl = Lvl; - type MaxLvl = Lvl; - type Guard = ($(MutexGuard<'a, R, $T>,)+); + type MinLvl = $first_G::Lvl; + type MaxLvl = $first_G::Lvl; + type Guard = ( + MutexGuard<'a, $first_G::RawMtx, $first_G::Data>, + $(MutexGuard<'a, $G::RawMtx, $G::Data>,)+ + ); fn collect_ids(&self, out: &mut alloc::vec::Vec) { + out.push(self.$first_idx.id()); $(out.push(self.$idx.id());)+ } - #[allow(clippy::indexing_slicing, non_snake_case)] + #[allow(clippy::expect_used, non_snake_case)] fn lock_sorted(&'a self, sorted_indices: &[usize]) -> Self::Guard { - $(let mut $T: Option> = None;)+ + let mut $first_T: Option> = None; + $(let mut $T: Option> = None;)+ for &target in sorted_indices { - $( - if let Some(g) = lock_if_match!(self, target, $idx) { - $T = Some(g); - continue; - } - )+ - unreachable!(); + match target { + $first_idx => $first_T = Some(self.$first_idx.lock_ref()), + $($idx => $T = Some(self.$idx.lock_ref()),)+ + _ => unreachable!(), + } } - ($( - $T.expect("lock_sorted: missing index in permutation"), - )+) + ( + $first_T.expect("lock_sorted: missing index in permutation"), + $($T.expect("lock_sorted: missing index in permutation"),)+ + ) } } }; } -impl_lockable_tuple_same_level!( 3: 0 A, 1 B, 2 C); -impl_lockable_tuple_same_level!( 4: 0 A, 1 B, 2 C, 3 D); -impl_lockable_tuple_same_level!( 5: 0 A, 1 B, 2 C, 3 D, 4 E); -impl_lockable_tuple_same_level!( 6: 0 A, 1 B, 2 C, 3 D, 4 E, 5 F); -impl_lockable_tuple_same_level!( 7: 0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G); -impl_lockable_tuple_same_level!( 8: 0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H); -impl_lockable_tuple_same_level!( 9: 0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I); -impl_lockable_tuple_same_level!(10: 0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J); -impl_lockable_tuple_same_level!(11: 0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K); -impl_lockable_tuple_same_level!(12: 0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L); +impl_lockable_tuple_same_level!( 3: 0 A GA, 1 B GB, 2 C GC); +impl_lockable_tuple_same_level!( 4: 0 A GA, 1 B GB, 2 C GC, 3 D GD); +impl_lockable_tuple_same_level!( 5: 0 A GA, 1 B GB, 2 C GC, 3 D GD, 4 E GE); +impl_lockable_tuple_same_level!( 6: 0 A GA, 1 B GB, 2 C GC, 3 D GD, 4 E GE, 5 F GF); +impl_lockable_tuple_same_level!( 7: 0 A GA, 1 B GB, 2 C GC, 3 D GD, 4 E GE, 5 F GF, 6 G GG); +impl_lockable_tuple_same_level!( 8: 0 A GA, 1 B GB, 2 C GC, 3 D GD, 4 E GE, 5 F GF, 6 G GG, 7 H GH); +impl_lockable_tuple_same_level!( 9: 0 A GA, 1 B GB, 2 C GC, 3 D GD, 4 E GE, 5 F GF, 6 G GG, 7 H GH, 8 I GI); +impl_lockable_tuple_same_level!(10: 0 A GA, 1 B GB, 2 C GC, 3 D GD, 4 E GE, 5 F GF, 6 G GG, 7 H GH, 8 I GI, 9 J GJ); +impl_lockable_tuple_same_level!(11: 0 A GA, 1 B GB, 2 C GC, 3 D GD, 4 E GE, 5 F GF, 6 G GG, 7 H GH, 8 I GI, 9 J GJ, 10 K GK); +impl_lockable_tuple_same_level!(12: 0 A GA, 1 B GB, 2 C GC, 3 D GD, 4 E GE, 5 F GF, 6 G GG, 7 H GH, 8 I GI, 9 J GJ, 10 K GK, 11 L GL); diff --git a/tests/integration.rs b/tests/integration.rs index fcf5862..bf3d7e5 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -86,8 +86,9 @@ mod lock_with { } #[test] + #[allow(clippy::indexing_slicing)] fn twelve_tuple() { - let mutexes: Vec> = (0..12).map(|i| Mutex::new(i)).collect(); + let mutexes: Vec> = (0..12).map(Mutex::new).collect(); lock_scope(|key| { let ((), _key) = key.lock_with( @@ -864,6 +865,55 @@ mod smart_pointers { assert_eq!(*p + *c, 30); }); } + + #[test] + fn arc_tuple_lockset() { + let a: Arc> = Arc::new(Mutex::new(10)); + let b: Arc> = Arc::new(Mutex::new(20)); + + let set = LockSet::new((&a, &b)); + lock_scope(|key| { + let ((ga, gb), _key) = key.lock(&set); + assert_eq!(*ga + *gb, 30); + }); + } + + #[test] + fn arc_tuple_lock_with() { + let a: Arc> = Arc::new(Mutex::new(10)); + let b: Arc> = Arc::new(Mutex::new(20)); + + let sum = lock_scope(|key| { + let (s, _key) = key.lock_with(&(&a, &b), |(ga, gb)| *ga + *gb); + s + }); + assert_eq!(sum, 30); + } + + #[test] + fn mixed_arc_bare_tuple_lockset() { + let arc: Arc> = Arc::new(Mutex::new(10)); + let bare: Mutex = Mutex::new(20); + + let set = LockSet::new((&arc, &bare)); + lock_scope(|key| { + let ((ga, gb), _key) = key.lock(&set); + assert_eq!(*ga + *gb, 30); + }); + } + + #[test] + fn arc_3tuple_lock_with() { + let a: Arc> = Arc::new(Mutex::new(1)); + let b: Arc> = Arc::new(Mutex::new(2)); + let c: Arc> = Arc::new(Mutex::new(3)); + + let sum = lock_scope(|key| { + let (s, _key) = key.lock_with(&(&a, &b, &c), |(ga, gb, gc)| *ga + *gb + *gc); + s + }); + assert_eq!(sum, 6); + } } mod raw_mutex_try_lock { @@ -902,7 +952,7 @@ mod display_and_debug { lock_scope(|key| { let m: Mutex = Mutex::new(42); let (guard, _key) = key.lock(&m); - let dbg = format!("{:?}", guard); + let dbg = format!("{guard:?}"); assert_eq!(dbg, "42"); }); } @@ -910,7 +960,7 @@ mod display_and_debug { #[test] fn mutex_debug() { let m: Mutex = Mutex::new(0); - let dbg = format!("{:?}", m); + let dbg = format!("{m:?}"); assert!(dbg.starts_with("Mutex")); assert!(dbg.contains("id")); } @@ -918,7 +968,7 @@ mod display_and_debug { #[test] fn mutex_key_debug() { lock_scope(|key| { - let dbg = format!("{:?}", key); + let dbg = format!("{key:?}"); assert!(dbg.contains("MutexKey")); }); } @@ -927,7 +977,7 @@ mod display_and_debug { fn lockset_debug() { let m: Mutex = Mutex::new(0); let set = LockSet::new(&m); - let dbg = format!("{:?}", set); + let dbg = format!("{set:?}"); assert!(dbg.starts_with("LockSet")); } }