rustrefactoringmutexborrowing

Mutable borrow to object inside Mutex - how to refactor?


I have the following pattern in many of my functions:

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

struct State { 
    value: i32
}

fn foo(data: Arc<Mutex<State>>) {
    let state = &mut data.lock().expect("Could not lock mutex");
    // mutate `state`
}

&mut *data.lock().expect("Could not lock mutex") is repeated over and over, so I would like to refactor it to a function, in order to write something like

let state = get_state(data); 

I've tried the following:

fn get_state(data: &Arc<Mutex<State>>) -> &mut State {
    &mut data.lock().expect("Could not lock mutex")
}

Which fails to compile with:

ERROR: cannot return value referencing temporary value

This makes me believe that data.state.lock().expect("...") returns by value. However, I can see the state being mutated through multiple foo calls on this playground.

What is going on here? Why does my seemingly simple refactor fail to compile?


EDIT:

I would expect the following to work as well:

fn get_state<'a>(data: &'a Arc<Mutex<State>>) -> &'a mut State {
    let state: &'a mut State = &mut data.lock().expect("Could not lock mutex");
    state
}

But it fails with:

   |
12 | fn get_state<'a>(data: &'a Arc<Mutex<State>>) -> &'a mut State {
   |              -- lifetime `'a` defined here
13 |     let state: &'a mut State = &mut data.lock().expect("Could not lock mutex");
   |                -------------        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
   |                |
   |                type annotation requires that borrow lasts for `'a`
14 |     state
15 | }
   | - temporary value is freed at the end of this statement

Why doesn't the lifetime of whatever is returned from lock match the one of the data parameter?


Solution

  • The lock() method returns MutexGuard instead of a direct reference to the guarded object. You are able to work with object reference, because MutexGuard implements Deref and DerefMut, but you still need the mutex-guard to be in scope, because when it goes out of scope the mutex lock will be released. Also the lifetime of the reference to the inner object is bound to the lifetime of the mutex guard, so the compiler will not allow you to use the reference to the inner object without the mutex guard.

    You can extract your common logic in a macro or a method, but it should return MutexGuard instead of a reference to the inner object.