I have a collection of collections. Each of the inner collections inside the outer collection is wrapped in a RwLock
. I need to be able to iterate over corresponding entries from each inner collection, with some of the inner collections being borrowed shared (and locked for read) and others being borrowed mutably (and locked for write).
Distilled example:
#![feature(array_methods)]
use std::{
collections::HashMap,
slice::{Iter, IterMut},
sync::{RwLock, RwLockReadGuard, RwLockWriteGuard},
};
type TestMap = HashMap<&'static str, RwLock<Vec<String>>>;
enum AccessType {
Shared(&'static str),
Exclusive(&'static str),
}
enum Accessor<'a> {
Shared(RwLockReadGuard<'a, Vec<String>>, Iter<'a, String>),
Exclusive(RwLockWriteGuard<'a, Vec<String>>, IterMut<'a, String>),
}
enum Access<'a> {
Shared(&'a String),
Exclusive(&'a mut String),
}
struct TestCollection {
map: TestMap,
}
struct TestCollectionAccessor<'a, const N: usize> {
accessors: [Accessor<'a>; N],
}
impl TestCollection {
fn access<const N: usize>(&self, types: [AccessType; N]) -> TestCollectionAccessor<N> {
TestCollectionAccessor { accessors: types.map(|access_type| {
match access_type {
AccessType::Shared(key) => {
let guard = self.map.get(key).unwrap().read().unwrap();
let iterator = guard.iter();
Accessor::Shared(guard, iterator)
},
AccessType::Exclusive(key) => {
let mut guard = self.map.get(key).unwrap().write().unwrap();
let iterator = guard.iter_mut();
Accessor::Exclusive(guard, iterator)
},
}
})}
}
}
impl<'a, const N: usize> Iterator for TestCollectionAccessor<'a, N> {
type Item = [Access<'a>; N];
fn next(&mut self) -> Option<Self::Item> {
let many = self.accessors.each_mut().map(|accessor| {
match accessor {
Accessor::Shared(_, x) => x.next().map(|x| Access::Shared(x)),
Accessor::Exclusive(_, x) => x.next().map(|x| Access::Exclusive(x)),
}
});
if many.iter().all(|x| x.is_some()) {
Some(many.map(Option::unwrap))
}
else {
None
}
}
}
fn main() {
let mut test = TestCollection { map: HashMap::new() };
test.map.insert("a", RwLock::new([
"foo", "bar", "baz", "bang",
].into_iter().map(str::to_string).collect()));
test.map.insert("b", RwLock::new([
"alpha", "beta", "gamma", "delta",
].into_iter().map(str::to_string).collect()));
let accesses = [
AccessType::Shared("a"),
AccessType::Exclusive("b"),
];
for x in test.access(accesses) {
let (left_string, right_string) = match x {
[Access::Shared(left_string), Access::Exclusive(right_string)]
=> (left_string, right_string),
_ => panic!(),
};
right_string.push(' ');
right_string.push_str(left_string);
}
for (key, value) in test.map.iter() {
println!("{:?} = {:?}", key, *value.read().unwrap());
}
}
This example wants to read-lock test.map.get("a")
, write-lock test.map.get("b")
, and modify each element of "b"
to be followed by its counterpart from "a"
. Ideal output:
"a" = ["foo", "bar", "baz", "bang"]
"b" = ["alpha foo", "beta bar", "gamma baz", "delta bang"]
(or the other way around, depending on a flip of the HashMap
coin)
The problem is that I want Accessor
to wrap its respective iterator, but that iterator borrows from the guard... and I can't just return the guard and the iterator, like I try to do in the above example, because that moves the guard while the guard is borrowed. I can make a custom iterator that iterates on a RwLock*Guard<Vec<String>>
instead of using Vec<String>
's iterators directly, but that gives me an icky feeling. In my real application, instead of a Vec<String>
, it's a complicated custom data structure, and I really don't want to have to implement all of the iteration logic twice. I want there to be a better way.
Any ideas?
P.S. I know this simple example could be done with test.map.get("a").unwrap().read().unwrap().iter().zip(test.map.get("b").unwrap().write().unwrap().iter_mut())
. Just trust me that the application this is distilled from can't be rewritten in those terms. Among other reasons, in the real application, the definition of "corresponding" is fairly complicated, so the real next
actually some hefty logic in it, and each value yielded from the outer iterator may consume more than one value from any given inner iterator.