diff options
| author | Mica White <botahamec@outlook.com> | 2026-08-26 20:46:31 -0400 |
|---|---|---|
| committer | Mica White <botahamec@outlook.com> | 2026-08-26 20:46:31 -0400 |
| commit | 55b3a2425b242fbc5c6e471f220eeb8b949e8751 (patch) | |
| tree | 5ceb7910b60cc7331024b7ce1d49ec259e0302a8 /src/context | |
| parent | 6f6e030ea7edb9d155ebf21b5d42936c20801b50 (diff) | |
Add tests
Diffstat (limited to 'src/context')
| -rw-r--r-- | src/context/context.rs | 158 | ||||
| -rw-r--r-- | src/context/guard.rs | 58 | ||||
| -rw-r--r-- | src/context/iterator.rs | 695 | ||||
| -rw-r--r-- | src/context/tuple.rs | 847 |
4 files changed, 1758 insertions, 0 deletions
diff --git a/src/context/context.rs b/src/context/context.rs new file mode 100644 index 0000000..adc684f --- /dev/null +++ b/src/context/context.rs @@ -0,0 +1,158 @@ +use std::marker::PhantomData; + +use crate::{ + context::{LockContext, LockingIterator, LockingTuple}, + lockable::{Lockable, OwnedLockable}, + ThreadKey, +}; + +impl<'l, L> LockContext<'l, L> { + pub(crate) const fn new(lockable: &'l L) -> Self + where + L: OwnedLockable, + { + Self { + key: None, + lockable, + } + } + + /// Unlocks all locks in the collection, returning the [`ThreadKey`]. + /// + /// This requires a mutable reference to the context, so it cannot be called + /// without first dropping any [`ContextGuard`]s that reference this context. + /// This method will also return `None` if the context has not been locked + /// with a `ThreadKey`. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(42), Mutex::new(true)); + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let tuple = ctx.tuple(key); + /// + /// let (use_other, tuple) = tuple.lock_1(); + /// if **use_other { + /// drop(use_other); + /// drop(tuple); + /// let key = ctx.unlock().unwrap(); + /// let tuple = ctx.tuple(key); + /// let (mut item, _) = tuple.lock_0(); + /// **item = 67; + /// } else { + /// drop(use_other); + /// drop(tuple); + /// }; + /// + /// let key = ctx.unlock().unwrap(); + /// let tuple = ctx.tuple(key); + /// let (number, _) = tuple.lock_0(); + /// assert_eq!(**number, 67); + /// ``` + /// + /// [`ContextGuard`]: `crate::context::ContextGuard` + pub fn unlock(&mut self) -> Option<ThreadKey> { + self.key.take() + } +} + +impl<L: Lockable> LockContext<'_, L> { + /// Creates a [`LockingTuple`], which can lock a subset of a tuple of locks, + /// in a specific order. + /// + /// Sometimes, partial allocation of locks is useful. For example, you may want + /// to acquire a lock on one item before deciding if the second item should be + /// locked. If the locks can be organized into a tuple, [`LockingTuple`] is + /// capable of doing exactly that. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(true), Mutex::new(42), Mutex::new(67)); + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let tuple = ctx.tuple(key); + /// + /// let (use_other, tuple) = tuple.lock_0(); + /// let number = if **use_other { + /// tuple.lock_2().0 + /// } else { + /// tuple.lock_1().0 + /// }; + /// assert_eq!(**number, 67); + /// ``` + pub fn tuple(&mut self, key: ThreadKey) -> LockingTuple<'_, L, L> { + unsafe { + self.key = Some(key); + + LockingTuple { + _lockable: PhantomData, + // safety: we just inserted a key + key: self.key.as_ref().unwrap_unchecked(), + tuple: self.lockable, + outer: (), + } + } + } +} + +impl<'l, L> LockContext<'l, L> +where + &'l L: IntoIterator, +{ + /// Creates a [`LockingIterator`] to iterate through a collection of locks + /// without locking everything at once. + /// + /// Sometimes, partial allocation of locks is useful. For example, you may + /// want to acquire a lock on the first element of a list before deciding if + /// the second element should be locked. If the list is iterable, then a + /// [`LockingIterator`] is capable of doing exactly that. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// let mut sum = 0; + /// while let Some(item) = iter.lock_next() { + /// sum += **item; + /// } + /// + /// assert_eq!(sum, 12); + /// ``` + // TODO: support scoped locks + // TODO: implement get_disjoint + // TODO: support some sort of index tower thing + #[expect(clippy::iter_not_returning_iterator)] + pub fn iter( + &mut self, + key: ThreadKey, + ) -> LockingIterator<'_, <&'l L as IntoIterator>::IntoIter> { + unsafe { + self.key = Some(key); + + LockingIterator { + // safety: we just inserted a key + key: self.key.as_ref().unwrap_unchecked(), + iterator: self.lockable.into_iter(), + outer: (), + } + } + } +} diff --git a/src/context/guard.rs b/src/context/guard.rs new file mode 100644 index 0000000..0898c1f --- /dev/null +++ b/src/context/guard.rs @@ -0,0 +1,58 @@ +use std::fmt::{Debug, Display}; +use std::hash::Hash; +use std::ops::{Deref, DerefMut}; + +use super::ContextGuard; + +#[mutants::skip] // hashing involves RNG and is hard to test +#[cfg(not(tarpaulin_include))] +impl<Guard: Hash, Key> Hash for ContextGuard<'_, Guard, Key> { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.guard.hash(state) + } +} + +// No implementations of Eq, PartialEq, PartialOrd, or Ord +// You can't implement both PartialEq<Self> and PartialEq<T> +// It's easier to just implement neither and ask users to dereference +// This is less of a problem when using the scoped lock API + +#[mutants::skip] +#[cfg(not(tarpaulin_include))] +impl<Guard: Debug, Key> Debug for ContextGuard<'_, Guard, Key> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&**self, f) + } +} + +impl<Guard: Display, Key> Display for ContextGuard<'_, Guard, Key> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&**self, f) + } +} + +impl<Guard, Key> Deref for ContextGuard<'_, Guard, Key> { + type Target = Guard; + + fn deref(&self) -> &Self::Target { + &self.guard + } +} + +impl<Guard, Key> DerefMut for ContextGuard<'_, Guard, Key> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.guard + } +} + +impl<Guard, Key> AsRef<Guard> for ContextGuard<'_, Guard, Key> { + fn as_ref(&self) -> &Guard { + &self.guard + } +} + +impl<Guard, Key> AsMut<Guard> for ContextGuard<'_, Guard, Key> { + fn as_mut(&mut self) -> &mut Guard { + &mut self.guard + } +} diff --git a/src/context/iterator.rs b/src/context/iterator.rs new file mode 100644 index 0000000..cc3bd7c --- /dev/null +++ b/src/context/iterator.rs @@ -0,0 +1,695 @@ +use std::{ + iter::{Fuse, Peekable, Skip, Take}, + marker::PhantomData, +}; + +use super::{ContextGuard, LockingIterator}; + +use crate::{ + context::LockingTuple, + lockable::{Lockable, RawLock, Sharable}, + ThreadKey, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TryLockNextError { + FinishedIteration, + WouldBlock, +} + +impl<'l, I, O> LockingIterator<'l, I, O> { + fn with_iterator<M>(self, f: impl FnOnce(I) -> M) -> LockingIterator<'l, M, O> { + LockingIterator { + key: self.key, + iterator: f(self.iterator), + outer: self.outer, + } + } + + /// Exit out of the current scope of the locking iterator into the parent. + /// + /// After using one the recurse methods, it is possible to regain access to + /// the parent by exiting out of the scope of the child. Doing this will make + /// it impossible to re-enter this scope again. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = ([Mutex::new(1), Mutex::new(2), Mutex::new(3)], Mutex::new(true)); + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let tuple = ctx.tuple(key); + /// let mut iter = tuple.recurse_0_iter(); + /// + /// let mut sum = 0; + /// while let Some(item) = iter.lock_next() { + /// sum += **item; + /// } + /// + /// let tuple = iter.exit(); + /// let (should_assert, _) = tuple.lock_1(); + /// if **should_assert { + /// assert_eq!(sum, 6); + /// } + /// ``` + pub fn exit(self) -> O { + self.outer + } +} + +impl<'c, L: Iterator<Item = I>, I: IntoIterator, O> LockingIterator<'c, L, O> { + /// Create a new `LockingIterator` based on the next element in the iterator. + /// + /// If a list contains a list of locks, then this method can be used to + /// recurse into the next element of the list. To go back to the parent scope, + /// use [`LockingIterator::exit`] on the new list. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [ + /// [Mutex::new(1), Mutex::new(2), Mutex::new(3)], + /// [Mutex::new(4), Mutex::new(5), Mutex::new(6)], + /// ]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// let mut sums = Vec::new(); + /// while let Some(mut list) = iter.recurse_next() { + /// let mut sum = 0; + /// while let Some(item) = list.lock_next() { + /// sum += **item; + /// } + /// sums.push(sum); + /// iter = list.exit(); + /// } + /// + /// assert_eq!(sums, vec![6, 15]); + /// ``` + pub fn recurse_next( + mut self, + ) -> Option<LockingIterator<'c, <I as IntoIterator>::IntoIter, Self>> { + if let Some(iterator) = self.iterator.next() { + Some(LockingIterator { + key: self.key, + iterator: iterator.into_iter(), + outer: self, + }) + } else { + None + } + } + + /// Create a new `LockingIterator` based on the next element in the iterator. + /// + /// If a list contains a list of locks, then this method can be used to + /// recurse into the next element of the list. To go back to the parent scope, + /// use [`LockingIterator::exit`] on the new list. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [ + /// [Mutex::new(1), Mutex::new(2), Mutex::new(3)], + /// [Mutex::new(4), Mutex::new(5), Mutex::new(6)], + /// ]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let iter = ctx.iter(key); + /// + /// let mut list = iter.recurse_last().unwrap(); + /// let mut sum = 0; + /// while let Some(item) = list.lock_next() { + /// sum += **item; + /// } + /// + /// assert_eq!(sum, 15); + /// ``` + pub fn recurse_last(self) -> Option<LockingIterator<'c, <I as IntoIterator>::IntoIter, O>> { + if let Some(iterator) = self.iterator.last() { + Some(LockingIterator { + key: self.key, + iterator: iterator.into_iter(), + outer: self.outer, + }) + } else { + None + } + } +} + +impl<'c, L: Iterator<Item = &'c T>, T: 'c, O> LockingIterator<'c, L, O> { + /// Create a new `LockingIterator` based on the next element in the iterator. + /// + /// If a list contains a list of locks, then this method can be used to + /// recurse into the next element of the list. To go back to the parent scope, + /// use [`LockingIterator::exit`] on the new list. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [ + /// (Mutex::new(true), Mutex::new(1)), + /// (Mutex::new(false), Mutex::new(2)), + /// (Mutex::new(true), Mutex::new(3)), + /// ]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// let mut sum = 0; + /// while let Some(tuple) = iter.recurse_next_tuple() { + /// let (should_count, mut tuple) = tuple.lock_0(); + /// if **should_count { + /// let num = tuple.lock_mut_1(); + /// sum += **num; + /// } + /// iter = tuple.exit(); + /// } + /// + /// assert_eq!(sum, 4); + /// ``` + pub fn recurse_next_tuple(mut self) -> Option<LockingTuple<'c, T, T, Self>> { + if let Some(tuple) = self.iterator.next() { + Some(LockingTuple { + key: self.key, + _lockable: PhantomData, + tuple, + outer: self, + }) + } else { + None + } + } + + /// Create a new `LockingIterator` based on the next element in the iterator. + /// + /// If a list contains a list of locks, then this method can be used to + /// recurse into the next element of the list. To go back to the parent scope, + /// use [`LockingIterator::exit`] on the new list. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [ + /// (Mutex::new(true), Mutex::new(1)), + /// (Mutex::new(false), Mutex::new(2)), + /// (Mutex::new(true), Mutex::new(3)), + /// ]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let iter = ctx.iter(key); + /// + /// let tuple = iter.recurse_last_tuple().unwrap(); + /// let (should_count, mut tuple) = tuple.lock_0(); + /// if **should_count { + /// let num = tuple.lock_mut_1(); + /// assert_eq!(**num, 3); + /// } else { + /// panic!(); + /// } + /// ``` + pub fn recurse_last_tuple(self) -> Option<LockingTuple<'c, T, T, O>> { + if let Some(tuple) = self.iterator.last() { + Some(LockingTuple { + key: self.key, + _lockable: PhantomData, + tuple, + outer: self.outer, + }) + } else { + None + } + } +} + +impl<'c, L: 'c + Iterator<Item = &'c I>, I: 'c + RawLock + Lockable, O> LockingIterator<'c, L, O> { + /// Advances the iterator, locking the next element and returning a guard to + /// the inner data. + /// + /// Returns `None` when iteration is finished. Individual iterator + /// implementations may choose to resume iteration, and so calling `next()` + /// again may or may not eventually start returning `Some(Item)` again at some + /// point. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// let mut sum = 0; + /// while let Some(item) = iter.lock_next() { + /// sum += **item; + /// } + /// + /// assert_eq!(sum, 12); + /// ``` + pub fn lock_next(&mut self) -> Option<ContextGuard<'c, <I as Lockable>::Guard<'c>, ThreadKey>> { + if let Some(lock) = self.iterator.next() { + unsafe { + lock.raw_write(); + let guard = lock.guard(); + + Some(ContextGuard { + _key: self.key, + guard, + }) + } + } else { + None + } + } + + /// Consumes the iterator, returning the last element, without locking any + /// other elements. + /// + /// This method will evaluate the iterator until it returns `None`. While + /// doing so, it keeps track of the current element. After `None` is returned, + /// `lock_last()` will then lock the last element it saw and return the + /// lock's data. + /// + /// # Panics + /// + /// This function might panic if the iterator is infinite. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let iter = ctx.iter(key); + /// + /// let last = iter.lock_last().unwrap(); + /// assert_eq!(**last, 8); + /// ``` + pub fn lock_last(self) -> Option<ContextGuard<'c, <I as Lockable>::Guard<'c>, ThreadKey>> { + self.iterator.last().map(|lock| unsafe { + lock.raw_write(); + let guard = lock.guard(); + + ContextGuard { + _key: self.key, + guard, + } + }) + } +} + +impl<'c, L: 'c + Iterator<Item = &'c I>, I: 'c + RawLock + Lockable, O> + LockingIterator<'c, Peekable<L>, O> +{ + /// Attempts to lock the next element and returning a guard to + /// the inner data. + /// + /// # Errors + /// + /// Returns `Err(TryLockNextError::FinishedIteration)` when iteration is + /// finished. Individual iterator implementations may choose to resume + /// iteration, and so calling `next()` again may or may not eventually start + /// returning `Some(Item)` again at some point. + /// + /// Returns `Err(TryLockNextError::WouldBlock)` if the next lock in the + /// iterator is already locked. This will not advance the iterator. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// use happylock::context::iterator::TryLockNextError; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key).peekable(); + /// + /// let mut sum = 0; + /// loop { + /// match iter.try_lock_next() { + /// Ok(item) => sum += **item, + /// Err(TryLockNextError::WouldBlock) => continue, + /// Err(TryLockNextError::FinishedIteration) => break, + /// } + /// } + /// + /// assert_eq!(sum, 12); + /// ``` + pub fn try_lock_next( + &mut self, + ) -> Result<ContextGuard<'c, <I as Lockable>::Guard<'c>, ThreadKey>, TryLockNextError> { + if let Some(lock) = self.iterator.peek().copied() { + unsafe { + if lock.raw_try_write() { + // safety: we just saw that there is a valid value + let lock = self.iterator.next().unwrap_unchecked(); + let guard = lock.guard(); + + Ok(ContextGuard { + _key: self.key, + guard, + }) + } else { + Err(TryLockNextError::WouldBlock) + } + } + } else { + Err(TryLockNextError::FinishedIteration) + } + } +} + +impl<'c, L: 'c + Iterator<Item = &'c I>, I: 'c + RawLock + Sharable, O> LockingIterator<'c, L, O> { + /// Advances the iterator, acquiring a shared lock to the next element and + /// returning a guard to the inner data. + /// + /// Returns `None` when iteration is finished. Individual iterator + /// implementations may choose to resume iteration, and so calling `next()` + /// again may or may not eventually start returning `Some(Item)` again at some + /// point. + /// + /// # Example + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [RwLock::new(1), RwLock::new(3), RwLock::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// let mut sum = 0; + /// while let Some(item) = iter.read_next() { + /// sum += **item; + /// } + /// + /// assert_eq!(sum, 12); + /// ``` + pub fn read_next( + &mut self, + ) -> Option<ContextGuard<'c, <I as Sharable>::ReadGuard<'c>, ThreadKey>> { + if let Some(lock) = self.iterator.next() { + unsafe { + lock.raw_read(); + let guard = lock.read_guard(); + + Some(ContextGuard { + _key: self.key, + guard, + }) + } + } else { + None + } + } + + /// Consumes the iterator, returning the last element with readonly access, + /// without locking any other elements. + /// + /// This method will evaluate the iterator until it returns `None`. While + /// doing so, it keeps track of the current element. After `None` is returned, + /// `lock_last()` will then lock the last element it saw and return the + /// lock's data. + /// + /// # Panics + /// + /// This function might panic if the iterator is infinite. + /// + /// # Example + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [RwLock::new(1), RwLock::new(3), RwLock::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let iter = ctx.iter(key); + /// + /// let last = iter.read_last().unwrap(); + /// assert_eq!(**last, 8); + /// ``` + pub fn read_last(self) -> Option<ContextGuard<'c, <I as Sharable>::ReadGuard<'c>, ThreadKey>> { + self.iterator.last().map(|lock| unsafe { + lock.raw_read(); + let guard = lock.read_guard(); + + ContextGuard { + _key: self.key, + guard, + } + }) + } +} + +impl<'c, L: 'c + Iterator<Item = &'c I>, I: 'c + RawLock + Sharable, O> + LockingIterator<'c, Peekable<L>, O> +{ + /// Attempts to acquire a shared lock the next element and returning a guard + /// to the inner data. + /// + /// # Errors + /// + /// Returns `Err(TryLockNextError::FinishedIteration)` when iteration is + /// finished. Individual iterator implementations may choose to resume + /// iteration, and so calling `next()` again may or may not eventually start + /// returning `Some(Item)` again at some point. + /// + /// Returns `Err(TryLockNextError::WouldBlock)` if the next lock in the + /// iterator is already locked. This will not advance the iterator. + /// + /// # Example + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// use happylock::context::iterator::TryLockNextError; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [RwLock::new(1), RwLock::new(3), RwLock::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key).peekable(); + /// + /// let mut sum = 0; + /// loop { + /// match iter.try_read_next() { + /// Ok(item) => sum += **item, + /// Err(TryLockNextError::WouldBlock) => continue, + /// Err(TryLockNextError::FinishedIteration) => break, + /// } + /// } + /// + /// assert_eq!(sum, 12); + /// ``` + pub fn try_read_next( + &mut self, + ) -> Result<ContextGuard<'c, <I as Sharable>::ReadGuard<'c>, ThreadKey>, TryLockNextError> { + if let Some(lock) = self.iterator.peek().copied() { + unsafe { + if lock.raw_try_read() { + // safety: we just saw that there is a valid value + let lock = self.iterator.next().unwrap_unchecked(); + let guard = lock.read_guard(); + + Ok(ContextGuard { + _key: self.key, + guard, + }) + } else { + Err(TryLockNextError::WouldBlock) + } + } + } else { + Err(TryLockNextError::FinishedIteration) + } + } +} + +impl<'l, L: Iterator, O> LockingIterator<'l, L, O> { + /// Advances the iterator, without locking the next element in the iterator. + /// + /// Returns `false` when iteration is finished. Individual iterator + /// implementations may choose to resume iteration, and so calling + /// `skip_next()` again may or may not eventually start returning `true` again + /// at some point. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// iter.skip_next(); + /// assert!(iter.lock_next().is_some_and(|v| **v == 3)); + /// ``` + pub fn skip_next(&mut self) -> bool { + self.iterator.next().is_some() + } + + /// Advances the iterator, skipping `n` elements without locking. + /// + /// See [`Iterator::skip`] for more information. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(3), Mutex::new(8)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// iter.skip_mut(2); + /// assert!(iter.lock_next().is_some_and(|v| **v == 8)); + /// ``` + pub fn skip_mut(&mut self, n: usize) { + for _ in 0..n { + self.iterator.next(); + } + } + + /// Returns the bounds on the remaining length of the iterator. + /// + /// See [`Iterator::size_hint`] for more information. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(2), Mutex::new(3)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key); + /// + /// assert_eq!((3, Some(3)), iter.size_hint()); + /// let _ = iter.skip_next(); + /// assert_eq!((2, Some(2)), iter.size_hint()); + /// ``` + #[must_use] + pub fn size_hint(&self) -> (usize, Option<usize>) { + self.iterator.size_hint() + } + + /// Creates a new [`LockingIterator`] that skips the first `n` elements. + /// + /// Unlike `skip_next` or `skip_mut`, this method does not modify the iterator + /// in place. Instead, it returns a new iterator which skips the first `n` + /// elements. + /// + /// See [`Iterator::skip`] for more information. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(2), Mutex::new(3)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key).skip(2); + /// + /// assert!(iter.lock_next().is_some_and(|v| **v == 3)); + /// assert!(iter.lock_next().is_none()); + /// ``` + #[must_use] + pub fn skip(self, n: usize) -> LockingIterator<'l, Skip<L>, O> { + self.with_iterator(|i| i.skip(n)) + } + + /// Creates a new [`LockingIterator`] that yields only the first `n` elements, + /// or fewer if the iterator ends sooner. + /// + /// See [`Iterator::take`] for more information. + /// + /// # Example + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(1), Mutex::new(2), Mutex::new(3)]; + /// let locks = OwnedLockCollection::new(data); + /// let mut ctx = locks.context(); + /// let mut iter = ctx.iter(key).take(2); + /// + /// assert!(iter.lock_next().is_some_and(|v| **v == 1)); + /// assert!(iter.lock_next().is_some_and(|v| **v == 2)); + /// assert!(iter.lock_next().is_none()); + /// ``` + #[must_use] + pub fn take(self, n: usize) -> LockingIterator<'l, Take<L>, O> { + self.with_iterator(|i| i.take(n)) + } + + /// Creates a new [`LockingIterator`] that ends after the first `None` + /// + /// See [`Iterator::fuse`] for more information + #[must_use] + pub fn fuse(self) -> LockingIterator<'l, Fuse<L>, O> { + self.with_iterator(Iterator::fuse) + } + + /// Creates a new [`LockingIterator`] which has access to the + /// [`try_lock_next`] and/or [`try_read_next`] methods. + /// + /// See [`Iterator::peekable`] for more information + /// + /// [`try_lock_next`]: `LockingIterator::try_lock_next` + /// [`try_read_next`]: `LockingIterator::try_read_next` + #[must_use] + pub fn peekable(self) -> LockingIterator<'l, Peekable<L>, O> { + self.with_iterator(Iterator::peekable) + } +} diff --git a/src/context/tuple.rs b/src/context/tuple.rs new file mode 100644 index 0000000..9bca72e --- /dev/null +++ b/src/context/tuple.rs @@ -0,0 +1,847 @@ +use std::marker::PhantomData; + +use crate::{ + context::{ContextGuard, LockingIterator, LockingTuple}, + lockable::{Lockable, RawLock, Sharable}, + ThreadKey, +}; + +impl<'c, A, B, O> LockingTuple<'c, A, B, O> { + fn transmute<C>(self) -> LockingTuple<'c, C, B, O> { + LockingTuple { + _lockable: PhantomData, + key: self.key, + tuple: self.tuple, + outer: self.outer, + } + } +} + +macro_rules! lock_impl { + ($self: expr, $field: tt) => { + unsafe { + $self.tuple.$field.raw_write(); + ( + ContextGuard { + _key: &$self.key, + guard: $self.tuple.$field.guard(), + }, + $self.transmute(), + ) + } + }; +} + +macro_rules! try_lock_impl { + ($self: expr, $field: tt) => { + unsafe { + if $self.tuple.$field.raw_try_write() { + Ok(( + ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.guard(), + }, + $self.transmute(), + )) + } else { + Err($self) + } + } + }; +} + +macro_rules! lock_mut_impl { + ($self: expr, $field: tt) => { + unsafe { + $self.tuple.$field.raw_write(); + ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.guard(), + } + } + }; +} + +macro_rules! try_lock_mut_impl { + ($self: expr, $field: tt) => { + unsafe { + if $self.tuple.$field.raw_try_write() { + Some(ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.guard(), + }) + } else { + None + } + } + }; +} + +macro_rules! read_impl { + ($self: expr, $field: tt) => { + unsafe { + $self.tuple.$field.raw_read(); + ( + ContextGuard { + _key: &$self.key, + guard: $self.tuple.$field.read_guard(), + }, + $self.transmute(), + ) + } + }; +} + +macro_rules! try_read_impl { + ($self: expr, $field: tt) => { + unsafe { + if $self.tuple.$field.raw_try_read() { + Ok(( + ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.read_guard(), + }, + $self.transmute(), + )) + } else { + Err($self) + } + } + }; +} + +macro_rules! read_mut_impl { + ($self: expr, $field: tt) => { + unsafe { + $self.tuple.$field.raw_read(); + ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.read_guard(), + } + } + }; +} + +macro_rules! try_read_mut_impl { + ($self: expr, $field: tt) => { + unsafe { + if $self.tuple.$field.raw_try_read() { + Some(ContextGuard { + _key: $self.key, + guard: $self.tuple.$field.read_guard(), + }) + } else { + None + } + } + }; +} + +macro_rules! recurse_impl { + ($self: expr, $field: tt) => { + LockingTuple { + _lockable: PhantomData, + key: $self.key, + tuple: &$self.tuple.$field, + outer: $self.transmute(), + } + }; +} + +macro_rules! recurse_iter_impl { + ($self: expr, $field: tt) => { + LockingIterator { + key: $self.key, + iterator: $self.tuple.$field.into_iter(), + outer: $self.transmute(), + } + }; +} + +type LockReturn<'a, 'context, Guarded, L, C, O> = ( + ContextGuard<'a, <Guarded as Lockable>::Guard<'a>, ThreadKey>, + LockingTuple<'context, L, C, O>, +); + +type TryLockReturn<'a, 'context, Guarded, L, C, O, This> = + Result<LockReturn<'a, 'context, Guarded, L, C, O>, This>; + +type ReadReturn<'a, 'context, Guarded, L, C, O> = ( + ContextGuard<'a, <Guarded as Sharable>::ReadGuard<'a>, ThreadKey>, + LockingTuple<'context, L, C, O>, +); + +type TryReadReturn<'a, 'context, Guarded, L, C, O, This> = + Result<ReadReturn<'a, 'context, Guarded, L, C, O>, This>; + +type RecurseReturn<'context, Inner, L, C, O> = + LockingTuple<'context, Inner, Inner, LockingTuple<'context, L, C, O>>; + +type RecurseIterReturn<'context, Inner, L, C, O> = LockingIterator< + 'context, + <&'context Inner as IntoIterator>::IntoIter, + LockingTuple<'context, L, C, O>, +>; + +impl<T, C, Outer> LockingTuple<'_, T, C, Outer> { + /// Exit out of the current scope of the locking tuple into the parent. + pub fn exit(self) -> Outer { + self.outer + } +} + +impl<'context, A: RawLock + Lockable, Outer> LockingTuple<'context, (A,), (A,), Outer> { + /// Lock the first element, and return a new tuple where the first element + /// is inaccessible. + #[must_use] + pub fn lock_0<'a>(self) -> LockReturn<'a, 'context, A, ((),), (A,), Outer> + where + 'context: 'a, + { + lock_impl!(self, 0) + } + + /// Attempts to lock the first element without blocking, and return a new tuple + /// where the first element is inaccessible. + /// + /// # Errors + /// + /// If the element is already locked, `Err` is returned with the original + /// tuple. + pub fn try_lock_0<'a>(self) -> TryLockReturn<'a, 'context, A, ((),), (A,), Outer, Self> + where + 'context: 'a, + { + try_lock_impl!(self, 0) + } + + /// Lock the first element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_0(&mut self) -> ContextGuard<'_, A::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 0) + } + + /// Attempts to lock the first element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_0(&mut self) -> Option<ContextGuard<'_, A::Guard<'_>, ThreadKey>> { + try_lock_mut_impl!(self, 0) + } +} + +impl<'context, A: RawLock + Sharable, Outer> LockingTuple<'context, (A,), (A,), Outer> { + /// Acquire a shared lock to the first element, and return a new tuple where + /// the first element is inaccessible. + #[must_use] + pub fn read_0<'a>(self) -> ReadReturn<'a, 'context, A, ((),), (A,), Outer> + where + 'context: 'a, + { + read_impl!(self, 0) + } + + /// Attempts to acquire a shared lock the first element without blocking, and + /// return a new tuple where the first element is inaccessible. + /// + /// # Errors + /// + /// If the element is already exclusively locked, `Err` is returned with the original + /// tuple. + pub fn try_read_0<'a>(self) -> TryReadReturn<'a, 'context, A, ((),), (A,), Outer, Self> + where + 'context: 'a, + { + try_read_impl!(self, 0) + } + + /// Acquire a shared lock to the first element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_0(&mut self) -> ContextGuard<'_, A::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 0) + } + + /// Attempts to acquire a shared lock the first element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_0(&mut self) -> Option<ContextGuard<'_, A::ReadGuard<'_>, ThreadKey>> { + try_read_mut_impl!(self, 0) + } +} + +impl<'context, A, O> LockingTuple<'context, (A,), (A,), O> { + /// Consume the tuple, and return the first element as a new tuple. + #[must_use] + pub fn recurse_0(self) -> RecurseReturn<'context, A, ((),), (A,), O> { + recurse_impl!(self, 0) + } + + /// Consume the tuple, and return the first element as a locking iterator. + pub fn recurse_0_iter(self) -> RecurseIterReturn<'context, A, ((),), (A,), O> + where + &'context A: IntoIterator, + { + recurse_iter_impl!(self, 0) + } +} + +impl<'context, A: RawLock + Lockable, B, B0, O> LockingTuple<'context, (A, B), (A, B0), O> { + /// Lock the first element, and return a new tuple where the first element + /// is inaccessible. + #[must_use] + pub fn lock_0<'a>(self) -> LockReturn<'a, 'context, A, ((), B), (A, B0), O> + where + 'context: 'a, + { + lock_impl!(self, 0) + } + + /// Lock the first element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_0(&mut self) -> ContextGuard<'_, A::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 0) + } + + /// Attempts to lock the first element without blocking, and return a new tuple + /// where the first element is inaccessible. + /// + /// # Errors + /// + /// If the element is already locked, `Err` is returned with the original + /// tuple. + pub fn try_lock_0<'a>(self) -> TryLockReturn<'a, 'context, A, ((), B), (A, B0), O, Self> + where + 'context: 'a, + { + try_lock_impl!(self, 0) + } + + /// Attempts to lock the first element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_0(&mut self) -> Option<ContextGuard<'_, A::Guard<'_>, ThreadKey>> { + try_lock_mut_impl!(self, 0) + } +} + +impl<'context, A: RawLock + Sharable, B, B0, O> LockingTuple<'context, (A, B), (A, B0), O> { + /// Acquire a shared lock to the first element, and return a new tuple where + /// the first element is inaccessible. + #[must_use] + pub fn read_0<'a>(self) -> ReadReturn<'a, 'context, A, ((), B), (A, B0), O> + where + 'context: 'a, + { + read_impl!(self, 0) + } + + /// Attempts to acquire a shared lock the first element without blocking, and + /// return a new tuple where the first element is inaccessible. + /// + /// # Errors + /// + /// If the element is already exclusively locked, `Err` is returned with the original + /// tuple. + pub fn try_read_0<'a>(self) -> TryReadReturn<'a, 'context, A, ((), B), (A, B0), O, Self> + where + 'context: 'a, + { + try_read_impl!(self, 0) + } + + /// Acquire a shared lock to the first element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_0(&mut self) -> ContextGuard<'_, A::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 0) + } + + /// Attempts to acquire a shared lock the first element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_0(&mut self) -> Option<ContextGuard<'_, A::ReadGuard<'_>, ThreadKey>> { + try_read_mut_impl!(self, 0) + } +} + +impl<'context, A, B, B0, O> LockingTuple<'context, (A, B), (A, B0), O> { + /// Consume the tuple, and return the first element as a new tuple. + #[must_use] + pub fn recurse_0(self) -> RecurseReturn<'context, A, ((), B), (A, B0), O> { + recurse_impl!(self, 0) + } + + /// Consume the tuple, and return the first element as a locking iterator. + pub fn recurse_0_iter(self) -> RecurseIterReturn<'context, A, ((), B), (A, B0), O> + where + &'context A: IntoIterator, + { + recurse_iter_impl!(self, 0) + } +} + +impl<'context, A: Lockable + RawLock, B, O> LockingTuple<'context, (A, B), (A, B), O> { + /// Lock the first element, and return the second element as a new tuple. + #[must_use] + pub fn lock_and_recurse<'a>( + self, + ) -> ( + ContextGuard<'a, <A as Lockable>::Guard<'a>, ThreadKey>, + LockingTuple<'context, B, B, O>, + ) + where + 'context: 'a, + { + unsafe { + self.tuple.0.raw_write(); + ( + ContextGuard { + _key: self.key, + guard: self.tuple.0.guard(), + }, + LockingTuple { + _lockable: PhantomData, + key: self.key, + tuple: &self.tuple.1, + outer: self.outer, + }, + ) + } + } +} + +impl<'context, A, A0, B: RawLock + Lockable, O> LockingTuple<'context, (A, B), (A0, B), O> { + /// Lock the second element, and return a new tuple where the first and second + /// elements are inaccessible. + #[must_use] + pub fn lock_1<'a>(self) -> LockReturn<'a, 'context, B, ((), ()), (A0, B), O> + where + 'context: 'a, + { + lock_impl!(self, 1) + } + + /// Lock the second element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_1(&mut self) -> ContextGuard<'_, B::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 1) + } + + /// Attempts to lock the second element without blocking, and return a new tuple + /// where the first element is inaccessible. + /// + /// # Errors + /// + /// If the element is already locked, `Err` is returned with the original + /// tuple. + pub fn try_lock_1<'a>(self) -> TryLockReturn<'a, 'context, B, ((), ()), (A0, B), O, Self> + where + 'context: 'a, + { + try_lock_impl!(self, 1) + } + + /// Attempts to lock the second element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_1(&mut self) -> Option<ContextGuard<'_, B::Guard<'_>, ThreadKey>> { + try_lock_mut_impl!(self, 1) + } +} + +impl<'context, A, A0, B: RawLock + Sharable, O> LockingTuple<'context, (A, B), (A0, B), O> { + /// Acquire a shared lock to the second element, and return a new tuple where + /// the second element is inaccessible. + #[must_use] + pub fn read_1<'a>(self) -> ReadReturn<'a, 'context, B, ((), ()), (A0, B), O> + where + 'context: 'a, + { + read_impl!(self, 1) + } + + /// Attempts to acquire a shared lock the second element without blocking, and + /// return a new tuple where the second element is inaccessible. + /// + /// # Errors + /// + /// If the element is already exclusively locked, `Err` is returned with the original + /// tuple. + pub fn try_read_1<'a>(self) -> TryReadReturn<'a, 'context, B, ((), ()), (A0, B), O, Self> + where + 'context: 'a, + { + try_read_impl!(self, 1) + } + + /// Acquire a shared lock to the second element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_1(&mut self) -> ContextGuard<'_, B::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 1) + } + + /// Attempts to acquire a shared lock the second element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_1(&mut self) -> Option<ContextGuard<'_, B::ReadGuard<'_>, ThreadKey>> { + try_read_mut_impl!(self, 1) + } +} + +impl<'context, A, A0, B, O> LockingTuple<'context, (A, B), (A0, B), O> { + /// Consume the tuple, and return the second element as a new tuple. + #[must_use] + pub fn recurse_1(self) -> RecurseReturn<'context, B, ((), ()), (A0, B), O> { + recurse_impl!(self, 1) + } + + /// Consume the tuple, and return the second element as a locking iterator. + pub fn recurse_1_iter(self) -> RecurseIterReturn<'context, B, (A, ()), (A0, B), O> + where + &'context B: IntoIterator, + { + recurse_iter_impl!(self, 1) + } +} + +impl<'context, A: RawLock + Lockable, B, B0, C, C0, O> + LockingTuple<'context, (A, B, C), (A, B0, C0), O> +{ + /// Lock the first element, and return a new tuple where the first element + /// is inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn lock_0<'a>(self) -> LockReturn<'a, 'context, A, ((), B, C), (A, B0, C0), O> + where + 'context: 'a, + { + lock_impl!(self, 0) + } + + /// Lock the first element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_0(&mut self) -> ContextGuard<'_, A::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 0) + } + + /// Attempts to lock the first element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_0(&mut self) -> Option<ContextGuard<'_, A::Guard<'_>, ThreadKey>> { + try_lock_mut_impl!(self, 0) + } +} + +impl<'context, A: RawLock + Sharable, B, B0, C, C0, O> + LockingTuple<'context, (A, B, C), (A, B0, C0), O> +{ + /// Acquire a shared lock to the first element, and return a new tuple where + /// the first element is inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn read_0<'a>(self) -> ReadReturn<'a, 'context, A, ((), B, C), (A, B0, C0), O> + where + 'context: 'a, + { + read_impl!(self, 0) + } + + /// Acquire a shared lock to the first element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_0(&mut self) -> ContextGuard<'_, A::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 0) + } + + /// Attempts to acquire a shared lock the first element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_0(&mut self) -> Option<ContextGuard<'_, A::ReadGuard<'_>, ThreadKey>> { + try_read_mut_impl!(self, 0) + } +} + +impl<'context, A, B, B0, C, C0, O> LockingTuple<'context, (A, B, C), (A, B0, C0), O> { + /// Consume the tuple, and return the first element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_0(self) -> RecurseReturn<'context, A, ((), B, C), (A, B0, C0), O> { + recurse_impl!(self, 0) + } + + /// Consume the tuple, and return the first element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_0_iter(self) -> RecurseIterReturn<'context, A, ((), B, C), (A, B0, C0), O> + where + &'context A: IntoIterator, + { + recurse_iter_impl!(self, 0) + } +} + +impl<'context, A, A0, B: RawLock + Lockable, C, C0, O> + LockingTuple<'context, (A, B, C), (A0, B, C0), O> +{ + /// Lock the second element, and return a new tuple where the first and second + /// elements are inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn lock_1<'a>(self) -> LockReturn<'a, 'context, B, ((), (), C), (A0, B, C0), O> + where + 'context: 'a, + { + lock_impl!(self, 1) + } + + /// Lock the second element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_1(&mut self) -> ContextGuard<'_, B::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 1) + } + + /// Attempts to lock the second element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_1(&mut self) -> Option<ContextGuard<'_, B::Guard<'_>, ThreadKey>> { + try_lock_mut_impl!(self, 1) + } +} + +impl<'context, A, A0, B: RawLock + Sharable, C, C0, O> + LockingTuple<'context, (A, B, C), (A0, B, C0), O> +{ + /// Acquire a shared lock to the second element, and return a new tuple where + /// the second element is inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn read_1<'a>(self) -> ReadReturn<'a, 'context, B, ((), (), C), (A0, B, C0), O> + where + 'context: 'a, + { + read_impl!(self, 1) + } + + /// Acquire a shared lock to the second element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_1(&mut self) -> ContextGuard<'_, B::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 1) + } + + /// Attempts to acquire a shared lock the second element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_1(&mut self) -> Option<ContextGuard<'_, B::ReadGuard<'_>, ThreadKey>> { + try_read_mut_impl!(self, 1) + } +} + +impl<'context, A, A0, B, C, C0, O> LockingTuple<'context, (A, B, C), (A0, B, C0), O> { + /// Consume the tuple, and return the second element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_1(self) -> RecurseReturn<'context, B, ((), (), C), (A0, B, C0), O> { + recurse_impl!(self, 1) + } + + /// Consume the tuple, and return the second element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_1_iter(self) -> RecurseIterReturn<'context, B, ((), (), C), (A0, B, C0), O> + where + &'context B: IntoIterator, + { + recurse_iter_impl!(self, 1) + } +} + +impl<'context, A, A0, B, B0, C: RawLock + Lockable, O> + LockingTuple<'context, (A, B, C), (A0, B0, C), O> +{ + /// Lock the third element, and return a new tuple where all elements are + /// inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn lock_2<'a>(self) -> LockReturn<'a, 'context, C, ((), (), ()), (A0, B0, C), O> + where + 'context: 'a, + { + lock_impl!(self, 2) + } + + /// Lock the third element. The tuple becomes unusable until the returned + /// guard is dropped. + #[must_use] + pub fn lock_mut_2(&mut self) -> ContextGuard<'_, C::Guard<'_>, ThreadKey> { + lock_mut_impl!(self, 2) + } + + /// Attempts to lock the third element without blocking. If successful, the + /// tuple becomes unusable until the returned guard is dropped. If the element + /// is already locked, `None` is returned. + #[must_use] + pub fn try_lock_mut_2(&mut self) -> Option<ContextGuard<'_, C::Guard<'_>, ThreadKey>> { + try_lock_mut_impl!(self, 2) + } +} + +impl<'context, A, A0, B, B0, C: RawLock + Sharable, O> + LockingTuple<'context, (A, B, C), (A0, B0, C), O> +{ + /// Acquire a shared lock to the third element, and return a new tuple where + /// the third element is inaccessible. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn read_2<'a>(self) -> ReadReturn<'a, 'context, C, ((), (), ()), (A0, B0, C), O> + where + 'context: 'a, + { + read_impl!(self, 2) + } + + /// Acquire a shared lock to the third element. The tuple becomes unusable + /// until the returned guard is dropped. + #[must_use] + pub fn read_mut_2(&mut self) -> ContextGuard<'_, C::ReadGuard<'_>, ThreadKey> { + read_mut_impl!(self, 2) + } + + /// Attempts to acquire a shared lock the third element without blocking. If + /// successful, the tuple becomes unusable until the returned guard is + /// dropped. If the element is already exclusively locked, `None` is returned. + #[must_use] + pub fn try_read_mut_2(&mut self) -> Option<ContextGuard<'_, C::ReadGuard<'_>, ThreadKey>> { + try_read_mut_impl!(self, 2) + } +} + +impl<'context, A, A0, B, B0, C, O> LockingTuple<'context, (A, B, C), (A0, B0, C), O> { + /// Consume the tuple, and return the third element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_2(self) -> RecurseReturn<'context, C, ((), (), ()), (A0, B0, C), O> { + recurse_impl!(self, 2) + } + + /// Consume the tuple, and return the third element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_2_iter(self) -> RecurseIterReturn<'context, C, ((), (), ()), (A0, B0, C), O> + where + &'context C: IntoIterator, + { + recurse_iter_impl!(self, 2) + } +} + +impl<'context, A, B, B0, C, C0, D, D0, O> LockingTuple<'context, (A, B, C, D), (A, B0, C0, D0), O> { + /// Consume the tuple, and return the first element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_0(self) -> RecurseReturn<'context, A, ((), B, C, D), (A, B0, C0, D0), O> { + recurse_impl!(self, 0) + } + + /// Consume the tuple, and return the first element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_0_iter(self) -> RecurseIterReturn<'context, A, ((), B, C, D), (A, B0, C0, D0), O> + where + &'context A: IntoIterator, + { + recurse_iter_impl!(self, 0) + } +} + +impl<'context, A, A0, B, C, C0, D, D0, O> LockingTuple<'context, (A, B, C, D), (A0, B, C0, D0), O> { + /// Consume the tuple, and return the second element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_1(self) -> RecurseReturn<'context, B, ((), (), C, D), (A0, B, C0, D0), O> { + recurse_impl!(self, 1) + } + + /// Consume the tuple, and return the second element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_1_iter( + self, + ) -> RecurseIterReturn<'context, B, ((), (), C, D), (A0, B, C0, D0), O> + where + &'context B: IntoIterator, + { + recurse_iter_impl!(self, 1) + } +} + +impl<'context, A, A0, B, B0, C, D, D0, O> LockingTuple<'context, (A, B, C, D), (A0, B0, C, D0), O> { + /// Consume the tuple, and return the third element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_2(self) -> RecurseReturn<'context, C, ((), (), (), D), (A0, B0, C, D0), O> { + recurse_impl!(self, 2) + } + + /// Consume the tuple, and return the third element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_2_iter( + self, + ) -> RecurseIterReturn<'context, C, ((), (), (), D), (A0, B0, C, D0), O> + where + &'context C: IntoIterator, + { + recurse_iter_impl!(self, 2) + } +} + +impl<'context, A, A0, B, B0, C, C0, D, O> LockingTuple<'context, (A, B, C, D), (A0, B0, C0, D), O> { + /// Consume the tuple, and return the fourth element as a new tuple. + #[must_use] + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_3(self) -> RecurseReturn<'context, D, ((), (), (), ()), (A0, B0, C0, D), O> { + recurse_impl!(self, 3) + } + + /// Consume the tuple, and return the first element as a locking iterator. + // The type is impossible to refactor, and I already wrote this function, so no point in removing it + #[expect(clippy::type_complexity)] + pub fn recurse_3_iter( + self, + ) -> RecurseIterReturn<'context, D, ((), (), (), ()), (A0, B0, C0, D), O> + where + &'context D: IntoIterator, + { + recurse_iter_impl!(self, 3) + } +} |
