Mutable borrow fail in Mutex

Viewed 194

I get an error about mutable borrow fail in Mutex.

Here is the code. It's Ok now. But it will fail to compile if uncommenting the 2 lines, which wraps the state into a Mutex and unwraps it out.

Why does the Mutex make the difference?

use std::collections::HashMap;
use std::sync::Mutex;

struct State {
    h1: HashMap<String, String>,
    h2: HashMap<String, String>,
}

fn main() {
    let mut state = State {
        h1: HashMap::new(),
        h2: HashMap::new(),
    };

    // It fails to compile if uncommenting these 2 lines!
    //let state = Mutex::new(state);
    //let mut state = state.lock().unwrap();

    let v1 = state.h1.get_mut("abc").unwrap();
    let v2 = state.h2.get_mut("abc").unwrap();
    v1.push_str("123");
    v2.push_str("123");
}

The error if uncommenting the 2 lines:

error[E0499]: cannot borrow `state` as mutable more than once at a time
  --> tmp.rs:20:14
   |
19 |     let v1 = state.h1.get_mut("abc").unwrap();
   |              ----- first mutable borrow occurs here
20 |     let v2 = state.h2.get_mut("abc").unwrap();
   |              ^^^^^ second mutable borrow occurs here
21 |     v1.push_str("123");
   |     -- first borrow later used here

===EDIT===

It's Ok if changing some code order:

    let state = Mutex::new(state);
    let mut state = state.lock().unwrap();

    let v1 = state.h1.get_mut("abc").unwrap();
    v1.push_str("123");
    let v2 = state.h2.get_mut("abc").unwrap();
    v2.push_str("123");

It seems that v1 holds a reference of state. If v1 ends, it drops the reference, and then state can be borrowed again.

Why v1 holds the reference of state?

====EDIT====

@Alexey Larionov gave the answer. And I find a similar question. And I should get the state out of the MutexGuard manually by deref_mut(). So adding this line makes things ok:

    let state = state.deref_mut();
2 Answers

By doing that you take 2 mutable references to the state itself. To solve it you could rescope one of the get_mut calls:

fn main() {
    let mut state = State {
        h1: HashMap::new(),
        h2: HashMap::new(),
    };

    // It fails to compile if uncommenting these 2 lines!
    let state = Mutex::new(state);
    let mut state = state.lock().unwrap();

    {
        let v1 = state.h1.get_mut("abc").unwrap();
        v1.push_str("123");
    }

    let v2 = state.h2.get_mut("abc").unwrap();
    v2.push_str("123");
}

That way, by the time you get the second reference, the first one is dropped already.

Playground

Just to elaborate @Netwave's answer a bit more, in case without a Mutex you don't get such an error, because by doing this

let mut state = State {
        h1: HashMap::new(),
        h2: HashMap::new(),
};
let v1 = state.h1.get_mut("abc").unwrap();
let v2 = state.h2.get_mut("abc").unwrap();

v1 mutably borrows state.h1, and v2 mutably borrows state.h2, and both of them count as immutable borrows of state in order to prevent moving the state out (e.g. let state2 = state; while other borrows exist). Since all the rules of borrowing are satisfied (either at most one mutable borrow per object or as many immutable borrows per object), borrowing is permitted.

In the Mutex case this line

let mut state = state.lock().unwrap();

makes the state of type MutexGuard::<State>. By using it in this way

let v1 = state.h1.get_mut("abc").unwrap();

Rust won't find h1 in MutexGuard::<State>, that's why it will use DerefMut trait to get &mut State implicitly from it. That's where you mutably borrow state. Since you later use the similar construction for v2 , you'll try to obtain a second mutable borrow on state, thus receiving the error

Related