Ok, so I want to make a NewType for Arc<Mutex<MyType>> because I want to encapsulate the locking in a public API. ie, locking should occur internally and users don't need to lock().unwrap() themselves all over the place, and I want to prevent that anyway.
My problem is that I can't seem to share my NewType across threads and mutate it. Maybe I am missing a trick, eg impl some missing Trait(s), or maybe it is just not possible...?
I discovered this issue when I tried to write a little program to test the locking in my NewType. The program runs a loop where each iteration reads all entries and also writes to all entries. The idea is to ensure that reads see a consistent (snapshot) view of the data. Here's a simplified version of that program, just wrapping a Vec<usize>.
use std::sync::{Arc, Mutex};
use std::thread;
struct MyVec{ inner: Arc<Mutex<Vec<usize>>>, }
impl MyVec {
fn new() -> Self {
Self{ inner: Arc::new(Mutex::new(vec![])) }
}
fn replace(&mut self, vals: &[usize]) {
let mut lock = self.inner.lock().unwrap();
lock.clear();
for v in vals.iter() {
lock.push(*v);
}
}
fn len(&self) -> usize {
self.inner.lock().unwrap().len()
}
fn push(&self, v: usize) {
self.inner.lock().unwrap().push(v);
}
fn set(&self, k: usize, v: usize) {
self.inner.lock().unwrap()[k] = v;
}
fn get_all(&self) -> Vec<usize> {
self.inner.lock().unwrap().clone()
}
}
fn main() {
let mut vec = MyVec::new();
for i in 0..500 {
vec.push(i);
}
let orig = vec.get_all();
thread::scope(|s| {
for _i in 0..100 {
let gets = s.spawn(|| {
assert_eq!(orig, vec.get_all());
});
let sets = s.spawn(|| {
for j in 0..vec.len() {
vec.set(j, 50);
}
});
let _ = gets.join();
let _ = sets.join();
vec.replace(&orig);
}
});
}
This won't compile.
error[E0502]: cannot borrow `vec` as mutable because it is also borrowed as immutable
However, if I replace main() with a version that wraps MyVec with an outer Arc<Mutex<..>>, then it works.
fn main() {
let vec = MyVec::new();
for i in 0..500 {
vec.push(i);
}
let orig = vec.get_all();
let vec = Arc::new(Mutex::new(vec));
thread::scope(|s| {
for _i in 0..100 {
let gets = s.spawn(|| {
assert_eq!(orig, vec.lock().unwrap().get_all());
});
let sets = s.spawn(|| {
let lock = vec.lock().unwrap();
for j in 0..lock.len() {
lock.set(j, 50);
}
});
let _ = gets.join();
let _ = sets.join();
vec.lock().unwrap().replace(&orig);
}
});
}
But that defeats the point of the NewType and I'm back where I started from.
So... is there some way to make the first version compile and run cleanly?
replace()needs to take&self, it will still work sinceMutexemploys interior mutability.However, note that your
assert!()will fail, since you're racing theset()and theget_all().Some other notes: instead of the loop in
replace()you can useextend_from_slice(), and theArcis redundant - you can use justMutex.