Mutex中可变借用失败

4

我在 Mutex 中遇到了可变借用失败的错误。

这是代码。 现在没问题了。但是,如果取消注释将 state 包装成 Mutex 并解包它,编译将会失败。

为什么 Mutex 会有所不同呢?

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");
}

取消注释2行代码后的错误:
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

如果改变一些代码顺序也没问题:

    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");
< p > 看起来v1持有state的引用。如果v1结束,它会丢弃引用,然后可以再次借用state

为什么v1持有state的引用?

====编辑====

@Alexey Larionov给出了答案。我发现一个类似的问题。我需要通过deref_mut()手动从MutexGuard中获取state。因此,添加这一行就可以解决问题:

    let state = state.deref_mut();

如果你足够勇敢(或鲁莽!),你可以实现借用分裂:https://stackoverflow.com/a/68581212/2588800。否则,你可以按照@Netwave的建议或使用“内部可变性”。 - Svetlin Zarev
2个回答

1

为了更详细地解释 @Netwave 的回答,假如没有使用 Mutex,你不会得到这样的错误,因为这样做

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 可变地借用了 state.h1,而 v2 可变地借用了 state.h2,它们都算作对 state 的不可变借用,以防止移动 state (例如在存在其他借用的情况下执行 let state2 = state;)。由于所有借用规则都得到了满足(每个对象最多只能有一个可变借用或者任意数量的不可变借用),所以可以进行借用。

Mutex 情况下,这一行代码

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

将类型为MutexGuard::<State>state创建出来。通过这种方式使用它。

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

Rust 在 MutexGuard::<State> 中找不到 h1,因此它将使用 DerefMut 特质来隐式获取 &mut State。这是你可变地借用 state 的地方。由于你后来对 v2 使用了类似的结构,因此会尝试在 state 上获得第二个可变借用,从而收到错误。

非常感谢。我不能像@Netwave建议的那样做,因为我必须先获取v1和v2(以确保键存在),然后更新它们两个。是否有其他方法让我像处理“state”一样处理“Mutex<state>”? - Bingzheng Wu
也许只需要 if(state.h1.contains_key("abc") && state.h2.contains_key("abc")) { /* Netwave的解决方案 */ }if条件应该对state进行不可变借用。 - Alexey Larionov

1

通过这样做,您将获取到状态本身的2个可变引用。要解决此问题,您可以重新定义其中一个get_mut调用的作用域:

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");
}

那样,当您获得第二个参考时,第一个已经被删除了。 游乐场

1
谢谢。即使没有{}块也可以。我在原问题中附加了一些代码。但我仍然想知道原因。 - Bingzheng Wu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接