Flat_map on a Vec<RwLock<Vec<T>>>

Here is a very simplified code where I am using i8 and Vec::iter just for the sake of an example, but the actual types and iterators are more involved (so &i8 or Vec::iter specific solutions are of little use).
Lets say

struct Foo(Vec<RwLock<Vec<i8>>>);

and I want to flat_map on the inner vectors:

fn iter(&self) -> impl Iterator<Item=&i8>

Below code does not compile because of temporary references and lifetimes:

impl Foo {
    fn iter(&self) -> impl Iterator<Item=&i8> {
        self.0.iter().flat_map(|item| {
            item.read().unwrap().iter()
        })
    }
}

See this link to playground for the compilation error.

However with a bit of unsafe code I can do:

use std::sync::{RwLock, RwLockReadGuard};

struct Foo(Vec<RwLock<Vec<i8>>>);

impl Foo {
    fn iter(&self) -> impl Iterator<Item = &i8> {
        self.0.iter().flat_map(|item| {
            let guard = item.read().unwrap();
            let xs: &Vec<i8> = unsafe { change_lifetime_const(&*guard) };
            GuardIter {
                _guard: guard,
                inner: xs.iter(),
            }
        })
    }
}

unsafe fn change_lifetime_const<'a, 'b, T>(x: &'a T) -> &'b T {
    &*(x as *const T)
}

struct GuardIter<'a, I> {
    _guard: RwLockReadGuard<'a, Vec<i8>>,
    inner: I,
}

impl<'a, I, T> Iterator for GuardIter<'a, I>
where
    I: Iterator<Item = T>,
{
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next()
    }
}

See this link to playground.

My questions:

  1. Is the above unsafe code correct? or, am I missing something?
  2. Any way this code can be improved?
  3. Or any better way to solve this problem?

Again, as a reminder, i8 and Vec::iter here are just for the sake of an example, but the actual types and iterators are more involved.

After .collect() finishes, every RwLockReadGuard will have been dropped, and you now have a Vec<&i8> pointing to i8s inside these Vecs that are no longer protected by the read locks.

Presumably, those RwLocks aren't just there for giggles, and you have other methods that obtain write locks. Those calls to .write() will now succeed, and you will end up with &mut Vec<i8>s that alias your &i8s. Push to one of these vecs and you may even end up with dangling pointers.

Your points makes sense.
But how would you solve this problem?

Here are a couple approaches.

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.