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>; //roughly

The 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>; //roughly

First 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>; //roughly

Within 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>; //roughly

Explicit 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.