rust - 如何重组结构,使得 'Cannot borrow ` x` as mutable more than once a time' 不会发生

标签 rust borrow-checker rust-tokio

目前,我有一个包含两个字段的 AppState 结构:

pub struct AppState {
    players: HashMap<String, PlayerValue>,
    cars: HashMap<String, CarValue>,
}

我已经使用 tokio 实现了一个多线程服务器应用程序,它通过改变 AppState 中的字段来处理请求并响应它们。

例如:

// Process stop request
let mut app_state = app_state_clone.lock().await; // `app_state_clone` is  `Arc<Mutex<AppState>>`
app_state.stop_players().await?;
app_state.stop_cars().await?;
Ok(())

此代码按预期编译和工作。然而,在这种情况下,我们首先等待 app_state.stop_players() future 的完成,然后再次等待 app_state.stop_cars() 的完成。

但是,由于这两种方法都可以指向结构的不同部分并且不会改变相同的字段,我想我可以只使用 try_join! 来同时等待任务。

// Process stop request
let mut app_state = app_state_clone.lock().await; // `app_state_clone` is  `Arc<Mutex<AppState>>`
let stop_players_f = app_state.stop_players();
let stop_cars_f = app_state.stop_cars();
try_join!(stop_players_f, stop_cars_f);
Ok(())

这将导致编译错误:

cannot borrow app_state as mutable more than once at a time

我已经搜索了重组我的代码以解决此问题的方法,并找到了以下 SO answer :

let mut x = X { a: 1, b: 2 };
let a = &mut x.a;
let b = &mut x.b;

Here the compiler can see that a and b never point to the same data, even though they do point inside of the same structure.

受此启发,我认为我可以按如下方式重构我的代码:

pub struct AppState {
    players_state: PlayersState,
    cars_state: CarsState,
}

pub struct PlayersState {
    players: HashMap<String, PlayerValue>,
}

pub struct CarsState {
    cars: HashMap<String, CarValue>,
}

服务器方法中的代码:

// Process stop request
let players_state = &mut app_state.players_state;
let cars_state = &mut app_state.cars_state;
let stop_players_f = players_state.stop_players();
let stop_cars_f = cars_state.stop_cars();
try_join!(stop_players_f, stop_cars_f);
Ok(())

但是,这只会导致同样的错误:

cannot borrow app_state as mutable more than once at a time

编辑:这是完整的编译错误:

error[E0499]: cannot borrow `app_state` as mutable more than once at a time
    --> crates/my-app/src/app.rs:1786:68
     |
1785 | ...                   let players_state = &mut app_state.players_state;
     |                                                --------- first mutable borrow occurs here
1786 | ...                    let cars_state = &mut app_state.cars_state;
     |                                              ^^^^^^^^^ second mutable borrow occurs here
...
1791 | ...                    let stop_players_f = players_state.stop_players();
     |                                              --------------------------- first borrow later used here

下面是 PlayersState 的实现:

impl PlayersState {
    pub fn players(&self) -> &HashMap<String, PlayerValue> {
        &self.players
    }

    pub fn players_mut(&mut self) -> &mut HashMap<String, PlayerValue> {
        &mut self.players
    }

    pub async fn stop_players(&self) -> Result<(), StopPlayersError> {
        for player in self.players.values() {
            match player {
                PlayerValue::MyOnePlayer(p) => {
                    p.stop().await?;
                }
            }
        }
        Ok(())
    }
}

注意:虽然 stop_players 中的 mut 不是必需的,但在 stop_cars 函数中是必需的。

将不胜感激对这个问题的更多见解,因为我似乎不明白如何解决这个问题。

编辑:

以下代码表示重现错误的实际最小示例:

use std::collections::HashMap;
use tokio::try_join;
use tokio::sync::Mutex;
use std::sync::Arc;

pub struct App {
    state: Arc<Mutex<AppState>>,
}

pub struct AppState {
    players_state: PlayersState,
    cars_state: CarsState,
}

pub enum PlayerValue {
    MyOnePlayer(PlayerInner)
}

pub struct PlayerInner;

impl PlayerInner {
    async fn stop(&self) -> Result<(), ()> { Ok(()) }
}

pub struct PlayersState {
    players: HashMap<String, PlayerValue>,
}

impl PlayersState {
    pub async fn stop_players(&self) -> Result<(), ()> {
        for player in self.players.values() {
            match player {
                PlayerValue::MyOnePlayer(p) => {
                    p.stop().await?;
                }
            }
        }
        Ok(())
    }
}

pub struct CarsState {
    cars: HashMap<String, ()>,
}

impl CarsState {
    async fn stop_cars(&mut self) -> Result<(), ()> { Ok(()) }
}

pub async fn check() -> Result<(), ()> {

    // Init on app startup 

    let state =  Arc::new(Mutex::new(AppState {
        players_state: PlayersState {
            players: vec![].into_iter().collect()
        },
        cars_state: CarsState {
            cars: vec![].into_iter().collect()
        },
    }));
    
    
    let app = App {
        state
    };
    
    
    // This code will be executed when we process a request
    // `app.state` is in the real 'code' a clone, because I have to use it in the request/response loop and UI loop
    
    let mut app_state = app.state.lock().await;
    
    let players_state = &mut app_state.players_state;
    let cars_state = &mut app_state.cars_state;
    let stop_players_f = players_state.stop_players();
    let stop_cars_f = cars_state.stop_cars();
    try_join!(stop_players_f, stop_cars_f);
    Ok(())
}

最佳答案

最小化示例:

use std::sync::Mutex;

pub struct AppState {
    players_state: (),
    cars_state: (),
}

pub fn check() {
    let state = Mutex::new(AppState {
        players_state: (),
        cars_state: (),
    });

    let mut app_state = state.lock().unwrap();
    let players_state = &mut app_state.players_state;
    let _cars_state = &mut app_state.cars_state;
    println!("{:?}", players_state);   
}

Playground
这里我们使用标准同步 Mutex , 但无论使用何种同步原语,错误本质上都是相同的。


此错误的原因是访问 app_state 上的属性必须通过 DerefMut implementation of MutexGuard .换句话说,有问题的部分实际上是这样的:

let players_state = &mut DerefMut::deref_mut(&mut app_state).players_state;
let _cars_state = &mut DerefMut::deref_mut(&mut app_state).cars_state;

DerefMut::deref_mut在借阅检查方面并不特别 - 它需要 &mut self ,因此它假设实现可以以任何方式访问任何字段,因此使任何先前存在的对该结构的引用无效。

然而,链接的答案并没有遇到这个问题,因为我们有 &mut X直接 - 在这种情况下,编译器能够推断出借用的不相交性,并允许对不同字段的引用共存。


因此,要获得相同的结果,您必须以某种方式转换 MutexGuard<AppState>&mut AppState在借用它的领域之前。幸运的是,如果我们不跨越函数边界(即不尝试向调用者返回任何东西),那很容易,而且上面的代码提示了如何做:可以简单地提取 desugared 的公共(public)部分代码:

let app_state: &mut AppState = DerefMut::deref_mut(&mut app_state);
let players_state = &mut app_state.players_state;
let _cars_state = &mut app_state.cars_state;

并且,自 deref_mut是解引用操作的实现,可以简化为:

let app_state = &mut *app_state;
let players_state = &mut app_state.players_state;
let _cars_state = &mut app_state.cars_state;

通过此更改,示例编译 - playground .

关于rust - 如何重组结构,使得 'Cannot borrow ` x` as mutable more than once a time' 不会发生,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74094456/

相关文章:

rust - 如何将元素从盒装切片中移出,在此过程中消耗切片?

opengl - 如何翻转OpenGL的三角形颜色?

rust - 将 Ref 返回到 Rc<RefCell<>> 内部的某些内容,而不使用 Ref::map

rust - 呼唤写!在 rust 中输出包裹在 Ok() 中的字符串

rust - 解包时无法移出共享引用背后的值

rust - 交错的作用域如何创建 “data race”? [复制]

rust - 不能借用为不可变的,因为它在函数参数中也被借用为可变的

rust - 为什么我无法使用 Tokio UnixStream 与 fork 子进程通信?

asynchronous - Tokio react 器是否轮询每个组合器之间所有可能的 poll() 函数?

rust - 为什么任务超时时不 panic ?