macro_rules! RelockMutexGuard {
{ $struct:ident ($($guard:tt)+) $(,)? [$($mutex:tt)+] $(,)?
$l:pat => $lock:expr,
$g:pat => $get_mutex:expr
$( , where $xbound:path )* $(,)?
} => { ... };
{ $struct:ident ($guard:ident $(:: $guardx:ident)*, $($mutex:tt)+) $(,)?
$l:pat => $lock:expr
$( , where $xbound:path )* $(,)?
} => { ... };
{ ($($guard:tt)+) $(,)? [$($mutex:tt)+] $(,)?
$l:ident => $lock:expr,
$g:ident => $get_mutex:expr
$( , where $xbound:path )* $(,)?
} => { ... };
{ ($guard:ident $(:: $guardx:ident)*, $($mutex:tt)+) $(,)?
$l:ident => $lock:expr
$( , where $xbound:path )* $(,)?
} => { ... };
{ < $($gen_lf0:lifetime, $($gen_lf1:lifetime,)*)? $($gen_ty:ident),* > $(,)?
( $guard_in:ty ) $(,)?
[ $mutexref:ty, $guard_out:path ] $(,)?
$l:pat => $lock:expr ,
$g:pat => $get_mutex:expr ,
where $t:ident : $($bound:tt)*
} => { ... };
}Expand description
Implements RelockMutexGuard (needed for any mutexes used
with a condvar)
Summary
Helper macro to impl RelockMutexGuard, for various mutex
types. RelockMutexGuard! has five forms, for five use cases:
- Third-party impl, convenient mutex: mutex ref recoverable from guard
- Third-party impl, inconveneint mutex ref passed separately
- First or second party impl, convenient mutex
- Within
async_condvar_fair, inconvenient mutex - Explicitly specify type and lifetime parameters.
An alternative to implementing RelockMutexGuard (via this
macro or otherwisse) is to use wait_no_relock everywhere.
Trait coherence, first/second party vs third party
The firt or second party forms can only be used in the crate
defining the mutex guard type (or within async_condvar_fair),
because of Rust’s trait coherence rules.
The third party forms define a helper struct, for pasing to
wait_baton or wait.
Convenient vs inconvenient mutexes
Ideally, mutex guards implement a method for recovering a reference to the original mutex. This saves users who need to unlock and relock a mutex (like anyone uusing a condition variable) from having to carry a separate copy of a reference to the mutex.
For convenient mutexes, you can just pass the guard to
wait_baton or wait. For inconvenient mutexes, you must
also pass a reference to the uneerlying mutex - typically, as
element .1 of a tuple or tuple struct.
Examples
Third party impl, convenient mutex
RelockMutexGuard!{
NiceGuardForCondvar(nice::MutexGuard) [nice::Mutex],
l => async move { l.lock() },
g => nice::MutexGuard::mutex(&g),
}
condvar.wait_baton(NiceGuardForCondvar(guard));Macro expansion:
struct NiceGuardForCondvar<'l,T>(nice::MutexGuard<'l,T>);
impl<'l,T> RelockMutexGuard for NiceGuardForCondvar<'l,T> {/*...*/}
async fn wait(&self, NiceGuardForCondvar<'l,T>) -> nice::MutexGuard<'l,T>; //roughlyThe expression after l => must be a a future, such as an async
block or an un-awaited call to an async function. l will have
type &'l MutexMT>. When awaited, the lock expression must yield
Guard<'l, T>.
The expression after g => must recover the mutex reference. g
will have type Guard<'l, T>, and the expression must have type
&'l Mutex<T>.
The mutex recovery expression is given ownership of the guard, but
it should discard it. (The expression forms the core of the
generated implementation of unlock_for_relock.)
The optional $xbounds are additional bounds on T (each with
their own where, contrary to normal Rust syntax). (This is
needed, for example, for RwLockReadGuard, which needs T: Send.)
Third party impl, inconvenient mutex
RelockMutexGuard!{
AwkwardGuardForCondvar(awkward::MutexGuard, awkward::Mutex),
l => async move { l.lock() },
}
condvar.wait_baton(AwkwardGuardForCondvar(guard, &mutex));Macro expansion:
struct AwkwardGuardForCondvar<'o,'i,T>(
awkward::MutexGuard<'i,T>,
&'o awkward::Mutex<T>,
);
impl<'o,'i,T> RelockMutexGuard for AwkwardGuardForCondvar<'o,'i,T> {/*...*/}
async fn wait(&self, AwkwardGuardForCondvar<'o,'i,T>) -> awkward::MutexGuard<'o,T>; //roughlyFirst and second-party impl’s
If you are invoking RelockMutexGuard! in the same crate as
defines a convenient mutex, or within async_condvar_fair, omit
the struct name.
This will implement the trait directly for the guard type, or the pair consisting of the guard and the mutex reference:
First or second party impl, convenient mutex
impl<'l,T> MutexGuard<'l,T> {
fn mutex(self_: &Self) -> &'l Mutex<T> { todo!() }
}
RelockMutexGuard!{
(MutexGuard) [Mutex],
l => async move { l.lock() },
g => MutexGuard::mutex(&g),
}Generates:
impl<'l,T> RelockMutexGuard for MutexGuard<'l,T> {/*...*/}
async fn wait(&self, MutexGuard<'l,T>) -> MutexGuard<'l,T>; //roughlyWithin async_condvar_fair, inconvenient mutex
RelockMutexGuard!{
(MutexGuard, Mutex),
l => async move { l.lock() },
}Generates:
impl<'o,'i,T> RelockMutexGuard for (MutexGuard<'i,T>, &'o Mutex<T>) {/*...*/}
async fn wait(&self, (MutexGuard<'i,T>, &'o Mutex<T>)) -> MutexGuard<'o,T>; //roughlyExplicit type and lifetime parameters
The other four forms all assume that the unlocked mutex is &...
and that the mutex is a wrapper type directly around T. For other
situations, for example Arc-based owned guards, the explicit form
is needed.
The other four forms are convenience wrappers that all expand to the explicit form.
RelockMutexGuard!{
<T>
(smol::lock::MutexGuardArc<T>)
[std::sync::Arc<smol::lock::Mutex<T>>, smol::lock::MutexGuardArc<T>],
l => async move { l.lock_arc().await },
g => smol::lock::MutexGuardArc::source(&g).clone(),
where T: 'static
}$guard_in is the argument to wait. $guard_out is the output
of the future. $mutexref is the intermediate, unlocked, form.
The first lifetime in the generic arguments (if there are any
lifetimes) becomes a bound on LockFuture.
$t: $bounds is expanded as $t : std::marker::Send + $bounds.