5

The following code in which a struct Counter wraps a u32. I am using Arc to wrap and Mutex to allow safe and shared access to the value. I have omitted the threaded code to provide a simple example:

use std::sync::{Arc, Mutex};

fn main() {
    #[derive(Debug)]
    struct Counter {
        count: u32,
    }

    let counter = Arc::new(Mutex::new(Counter { count: 0 }));

    for _ in 0..10 {
        let mut c_int = counter.lock().unwrap();
        c_int.count += 1;
    }

    println!("{:?}", counter.lock().unwrap());
}

Here counter.lock().unwrap() is able to transparently lock the mutex and unwrap the result and I don't need to dereference the Arc. Also dereference on c_int works transparently.

Consider following code where Counter is replaces with u32:

use std::sync::{Arc, Mutex};

fn main() {
    let counter = Arc::new(Mutex::new(32));

    for _ in 0..10 {
        let mut c_int = counter.lock().unwrap();
        c_int += 1;
    }

    println!("{:?}", counter.lock().unwrap());
}

It wouldn't compile because c_int += 1 is not valid as it doesn't derefernces to u32.

My questions are:

  1. Why structs are special while primitives are not while using through a smart pointer like Mutex?

  2. How I can use functions of Mutex directly on an Arc?

I think both have to do with Deref but not sure how.

Xolve
  • 22,298
  • 21
  • 77
  • 125

1 Answers1

5

c_int is not actually the integer or Counter instance, it's a std::sync::MutexGuard<'_, T>.

The reason why c_int.count += 1; works in the first example is Deref coercion: Wherever it sees an expression such as foo.bar, the compiler checks if foo has a member bar, and if it doesn't, it dereferences it, and tries again. In your case, because c_int is a mutex guard, it doesn't have a count member, the compiler will try (*c_int).count.

Now, std::sync::MutexGuard<'_, T> does implement Deref<Target=T>, which in your case means you can get a Counter out of the mutex guard, and the compiler stops there.

However, in the integer case, you only have c_int += 1, assignment by itself does not trigger Deref coercion, so the compiler rightfully gives you an error

8 |         (c_int) += 1;
  |         -------^^^^^
  |         |
  |         cannot use `+=` on type `std::sync::MutexGuard<'_, {integer}>`

To get the second example to work, you have to dereference yourself:

*c_int += 1;

See also:

mcarton
  • 27,633
  • 5
  • 85
  • 95