Search code examples
multithreadingrustnewtype

How can I NewType wrap an Arc<Mutex<..>> and mutate it across scoped threads?


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

playground

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.

playground

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?


Solution

  • replace() needs to take &self, it will still work since Mutex employs interior mutability.

    However, note that your assert!() will fail, since you're racing the set() and the get_all().

    Some other notes: instead of the loop in replace() you can use extend_from_slice(), and the Arc is redundant - you can use just Mutex.