Is this possible: MutexGuard<Iter<...>> from Mutex<HashMap<...>>

I have a concurrent data structure that contains map: Mutex<HashMap<K, V>> as one of its fields. My goal is to implement a locked iterator API to this field that keeps the lock guard during the iteration.

Something like:

pub struct LockedIter<'a, K, V> {
    inner: MutexGuard<'a, hash_map::Iter<'a, K, V>>
}

impl<'a, K, V> Iterator for LockedIter<'a, K, V> {
    type Item = (&'a K, &'a V);
    // ...
}

What would be the best way to implement this?

I tried parking_lot's MappedMutexGuard with MutexGuard::map(self.map.lock(), |hash_map| &mut hash_map.iter()), but it doesn't work in this situation due to the captured lifetime in LockedIter that doesn't interact well with HRTB.

You can build custom "mapped" smart pointers (and a mutex guard is a kind of smart pointer) using ouroboros. I'm not completely sure if it'll work out for iterator mutations, but it's worth a try.

First, re-using a lifetime parameter at two "levels" like MutexGuard<'a, hash_map::Iter<'a, K, V>> does is an antipattern and almost never what you want (even when both lifetimes are in covariant position, as here). Your default should be to use two different lifetimes:

pub struct LockedIter<'a, 'b, K, V> {
    inner: MutexGuard<'a, hash_map::Iter<'b, K, V>>,
}

If you try to write code against the one-lifetime version you're liable to run into puzzling compiler errors.

Second, you can get &'short T or &'short mut T from &'short MutexGuard<'long, T> via the Deref and DerefMut implementations, but you can't get &'long T or &'long mut T from MutexGuard<'long, T>, because it would allow this (playground):

fn do_something_unsound(_guard: MutexGuard<'long, i32>) -> &'long i32 {
    ...
}

fn break_stuff() {
    let mutex = Mutex::new(123);
    {
        let borrowed_mutex /* : &'long Mutex<i32> */ = &mutex;
        let first_borrow /* : &'long i32 */ = {
            let guard /* : MutexGuard<'long, i32> */ = Mutex::lock(borrowed_mutex).unwrap();
            do_something_unsound(guard)
            // guard is dropped here, but first_borrow will outlive it
        };
        // create and use a mutable reference to the contained value
        // this *should* invalidate any existing references to the value...
        *mutex.lock().unwrap() = 456;
        // ...but first_borrow is still valid here!
        dbg!(first_borrow);
    }
}

So I don't think what you're envisioning is possible: the yielded items would have to borrow from the LockedIter, which Iterator doesn't support (but a future GAT-enabled LendingIterator trait would). Instead, how about an "internal iteration" approach?

impl<'a, 'b: 'a, K, V> LockedIter<'a, 'b, K, V> {
    pub fn for_each_pair<F>(mut self, mut f: F)
    where
        F: FnMut(&K, &V),
    {
        for (k, v) in &mut *self.inner {
            f(k, v);
        }
    }
}

You can also have other methods in the for_each mold that support returning early, passing data between iterations (like fold), etc.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.