VP.
VP.

Reputation: 16775

How to avoid mutex borrowing problems when using it's guard

I want my method of struct to perform in a synchronized way. I wanted to do this by using Mutex (Playground):

use std::sync::Mutex;
use std::collections::BTreeMap;

pub struct A {
    map: BTreeMap<String, String>,
    mutex: Mutex<()>,
}

impl A {
    pub fn new() -> A {
        A {
            map: BTreeMap::new(),
            mutex: Mutex::new(()),
        }
    }
}

impl A {
    fn synchronized_call(&mut self) {
        let mutex_guard_res = self.mutex.try_lock();
        if mutex_guard_res.is_err() {
            return
        }
        let mut _mutex_guard = mutex_guard_res.unwrap(); // safe because of check above
        let mut lambda = |text: String| {
            let _ = self.map.insert("hello".to_owned(),
                                    "d".to_owned());
        };
        lambda("dd".to_owned());
    }
}    

Error message:

error[E0500]: closure requires unique access to `self` but `self.mutex` is already borrowed
  --> <anon>:23:26
   |
18 |         let mutex_guard_res = self.mutex.try_lock();
   |                               ---------- borrow occurs here
...
23 |         let mut lambda = |text: String| {
   |                          ^^^^^^^^^^^^^^ closure construction occurs here
24 |             if let Some(m) = self.map.get(&text) {
   |                              ---- borrow occurs due to use of `self` in closure
...
31 |     }
   |     - borrow ends here

As I understand when we borrow anything from the struct we are unable to use other struct's fields till our borrow is finished. But how can I do method synchronization then?

Upvotes: 2

Views: 2612

Answers (2)

user4815162342
user4815162342

Reputation: 155630

Rust mutexes do not work the way you are trying to use them. In Rust, a mutex protects specific data relying on the borrow-checking mechanism used elsewhere in the language. As a consequence, declaring a field Mutex<()> doesn't make sense, because it is protecting read-write access to the () unit object that has no values to mutate.

As Lukas explained, your call_synchronized as declared doesn't need to do synchronization because its signature already requests an exclusive (mutable) reference to self, which prevents it from being invoked from multiple threads on the same object. In other words, you need to change the signature of call_synchronized because the current one does not match the functionality it is intended to provide.

call_synchronized needs to accept a shared reference to self, which will signal to Rust that it can be called from multiple threads in the first place. Inside call_synchronized a call to Mutex::lock will simultaneously lock the mutex and provide a mutable reference to the underlying data, carefully scoped so that the lock is held for the duration of the reference:

use std::sync::Mutex;
use std::collections::BTreeMap;

pub struct A {
    synced_map: Mutex<BTreeMap<String, String>>,
}

impl A {
    pub fn new() -> A {
        A {
            synced_map: Mutex::new(BTreeMap::new()),
        }
    }
}

impl A {
    fn synchronized_call(&self) {
        let mut map = self.synced_map.lock().unwrap();
        // omitting the lambda for brevity, but it would also work
        // (as long as it refers to map rather than self.map)
        map.insert("hello".to_owned(), "d".to_owned());
    }
}

Upvotes: 4

Lukas Kalbertodt
Lukas Kalbertodt

Reputation: 89016

The closure needs a mutable reference to the self.map in order to insert something into it. But closure capturing works with whole bindings only. This means, that if you say self.map, the closure attempts to capture self, not self.map. And self can't be mutably borrowed/captured, because parts of self are already immutably borrowed.

We can solve this closure-capturing problem by introducing a new binding for the map alone such that the closure is able to capture it (Playground):

let mm = &mut self.map;
let mut lambda = |text: String| {
    let _ = mm.insert("hello".to_owned(), text);
};
lambda("dd".to_owned());

However, there is something you overlooked: since synchronized_call() accepts &mut self, you don't need the mutex! Why? Mutable references are also called exclusive references, because the compiler can assure at compile time that there is only one such mutable reference at any given time.

Therefore you statically know, that there is at most one instance of synchronized_call() running on one specific object at any given time, if the function is not recursive (calls itself).

If you have mutable access to a mutex, you know that the mutex is unlocked. See the Mutex::get_mut() method for more explanation. Isn't that amazing?

Upvotes: 8

Related Questions