Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusing automatic dereferencing of Arc

Tags:

rust

This is an example taken from the Mutex documentation:

use std::sync::{Arc, Mutex};
use std::sync::mpsc::channel;
use std::thread;

const N: usize = 10;
fn main() {
    let data = Arc::new(Mutex::new(0));
    let (tx,rx) = channel();
    for _ in 0..N{
        let (data, tx) = (data.clone(), tx.clone());
        thread::spawn(move || {
            // snippet
        });
    }
    rx.recv().unwrap();
}

My question is where the snippet comment is. It is given as

let mut data = data.lock().unwrap();
*data += 1;
if *data == N {
    tx.send(()).unwrap();
}

The type of data is Arc<Mutex<usize>>, so when calling data.lock(), I assumed that the Arc is being automatically dereferenced and an usize is assigned to data. Why do we need a *in front of data again to dereference it?

The following code which first dereferences the Arc and then proceeds with just an usize also works in place of the snippet.

let mut data = *data.lock().unwrap();
data += 1;
if data == N {
    tx.send(()).unwrap();
}   
like image 444
Aravindh S Avatar asked Dec 11 '22 09:12

Aravindh S


1 Answers

Follow the docs. Starting with Arc<T>:

  • Does Arc::lock exist? No. Check Deref.
  • Deref::Target is T. Check Mutex<T>.
  • Does Mutex::lock exist? Yes. It returns LockResult<MutexGuard<T>>.
  • Where does unwrap come from? LockResult<T> is a synonym for Result<T, PoisonError<T>>. So it's Result::unwrap, which results in a MutexGuard<T>.
  • Therefore, data is of type MutexGuard<usize>.

So this is wrong:

so when calling data.lock(), I assumed that the Arc is being automatically dereferenced and an usize is assigned to data.

Thus the question is not why you can't assign directly, but how you're able to assign an usize value at all. Again, follow the docs:

  • data is a MutexGuard<usize>, so check MutexGuard<T>.
  • *data is a pointer dereference in a context that requires mutation. Look for an implementation of DerefMut.
  • It says that for MutexGuard<T>, it implements DerefMut::deref_mut(&mut self) -> &mut T.
  • Thus, the result of *data is &mut usize.

Then we have your modified example. At this point, it should be clear that this is not at all doing the same thing: it's mutating a local variable that happens to contain the same value as the mutex. But because it's a local variable, changing it has absolutely no bearing on the contents of the mutex.


Thus, the short version is: the result of locking a mutex is a "smart pointer" wrapping the actual value, not the value itself. Thus you have to dereference it to access the value.

like image 92
DK. Avatar answered Jan 24 '23 14:01

DK.