`Arc<Mutex<T>>` and `MutexGuard` pack

I am not good at English. Sorry if there are any funny expressions.


My situation

I have Arc<Mutex<T>> and MutexGuard<'a, T> from it.
I want to hold MutexGuard for a while, instead of repeatedly acquiring and releasing it.
And I want to pass MutexGuard between functions.

My Attempt

If I carry only MutexGuard, the reference count of Arc might drop to zero at some time.
To prevent this, it seems that I need a type that packs both Arc and MutexGuard together.

Code with unsafe

Following is my code. Oh! there is unsafe...
I almost always mistake with it.
Moreover, this time is more creepy.
Because this seems like a common situation to me.
Please let me know if there are any mistakes.

use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Mutex, MutexGuard};

fn main() {
    let var = Arc::new(Mutex::new(1));
    let mut ag = sub(&var);
    *ag += 1;
    assert_eq!(*ag, 2);
}

fn sub<T>(var: &Arc<Mutex<T>>) -> ArcGuard<'_, T> {
    ArcGuard::new(&var)
}

struct ArcGuard<'a, T: 'a> {
    _arc: Arc<Mutex<T>>,
    guard: MutexGuard<'a, T>,
}

impl<'a, T: 'a> ArcGuard<'a, T> {
    pub fn new(arc: &Arc<Mutex<T>>) -> Self {
        let arc = arc.clone();
        let arc_ptr = &arc as *const Arc<Mutex<T>>;
        let guard = unsafe { (&*arc_ptr).lock().unwrap() };
        Self {
            _arc: arc,
            guard,
        }
    }
}

impl<'a, T: 'a> Deref for ArcGuard<'a, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &*self.guard
    }
}

impl<'a, T: 'a> DerefMut for ArcGuard<'a, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut *self.guard
    }
}

No, because the std MutexGuard carries a borrow of the Mutex, which requires keeping the Arc borrowed, and the Arc cannot drop while borrowed. Unless someone writes some broken unsafe, anyway.

Here's your playground with no unsafe. But it doesn't solve any problems.

2 Likes

Wow! I might have misunderstood Arc and Rc.

I thought the reference counter mechanism of Arc<T> didn't depend on T.
It's shocking to me that if T is Mutex, it won't be released while MutexGuard exists.
This is pretty fundamental stuff, right? ...I need to go back and study this again.

I thought the reference counter mechanism of Arc<T> didn't depend on T .

It doesn't. The counter is only changed when an Arc<T> is cloned or dropped. But, if you borrow &Mutex<T> from an Arc<Mutex<T>>, you will be unable to drop that Arc (because if you could, that would be a use-after-free bug). And, any time you create a MutexGuard, you must have borrowed a Mutex to do it. So the desired outcome occurs (must occur) even though Arc and MutexGuard are not cooperating.

2 Likes

It doesn't. The borrow checker does the work here, not Arc and Mutex*. Let's look at an example in slow motion.

    let var: Arc<Mutex<i32>> = Arc::new(Mutex::new(1));

    // (A)
    // let mtx_ref = <Arc<_> as Deref>::deref(&var);
    let mtx_ref: &Mutex<i32> = &*var;

    // (B)
    // let mut guard = Mutex::lock(mtx_ref).unwrap();
    let mut guard: MutexGuard<'_, i32> = mtx_ref.lock().unwrap();

    // (C)
    // This will result in an error.
    // error[E0505]: cannot move out of `var` because it is borrowed
    // drop(var);

    // (D)
    // *<MutexGuard<'_, _> as DerefMut>::deref_mut(&mut guard) += 1;
    *guard += 1;

Signatures:

// (A)   The lifetimes of these are connected
//       v---------v
fn deref(&self) -> &Self::Target;

// (B)
//          v-------------------------------vv
pub fn lock(&self) -> LockResult<MutexGuard<'_, T>>

// (D)
//           v-------------v
fn deref_mut(&mut self) -> &mut Self::Target;

The meaning of these function signatures is "so long as the return value is in use, the self remains borrowed".

So at (A), so long as mtx_ref is in use, var remains borrowed.

And at (B), so long as guard is in use, *mtx_ref remains borrowed. Which means var also remains borrowed.

And at (D), the same thing happens with a temporary. But also, this is a use of the guard, so *mtx_ref and var must still be borrowed.

You can't drop things which are borrowed. That prevents, among other things, dangling references. That's why you get an error if you uncomment the drop at (C).

The error says:

error[E0505]: cannot move out of `var` because it is borrowed
  --> src/main.rs:15:10
   |
5  |     let var: Arc<Mutex<i32>> = Arc::new(Mutex::new(1));
   |         --- binding `var` declared here
...
8  |     let mtx_ref: &Mutex<i32> = &*var;
   |                                  --- borrow of `var` occurs here
...
15 |     drop(var);
   |          ^^^ move out of `var` occurs here
...
18 |     *guard += 1;
   |      ----- borrow later used here

Which is basically what I just wrote in a different form.

Note that this example didn't involve reference counting at all. Here's a similar playground from a borrow-checking perspective, using only nested fields.

1 Like

Thank you all. I think I understand now.

I had overlooked the reference within the same type from guard to arc (cloned to _arc).

I guess my brain was shaken up recently hearing about Pin's self-references.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.