目前,我有一个AppState struct ,它有两个字段:

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()的完成.

然而,由于这两种方法都可以指向 struct 的不同部分,并且不会改变相同的字段,所以我认为可以使用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(())

这将导致编译错误:

不能一次多次borrow app_state作为可变变量

我一直在寻找重新组织代码的方法来修复这个问题,并找到了以下answer个:

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

在这里,编译器可以看到ab从不指向相同的数据,即使它们确实指向相同的 struct 内部.

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

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(())

然而,这只会导致相同的错误:

不能一次多次borrow app_state作为可变变量

编辑:以下是完整的编译错误:

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函数中是必需的.

如果能对这个问题有更多的见解,我将不胜感激,因为我似乎不知道如何才能解决这个问题.

EDIT:

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

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
Here we're using the standard sync Mutex, but the error will be essentially the same no matter what synchronization primitive one uses.


此错误的原因是访问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;

当涉及到borrow 判断时,DerefMut::deref_mut并不是特别的-它需要&mut self,所以它假设实现可以以任何方式访问任何字段,从而使先前存在的对该 struct 的任何引用无效.

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


因此,要获得相同的结果,您必须在borrow 其字段之前以某种方式将MutexGuard<AppState>转换为&mut AppState.幸运的是,如果我们不跨越函数边界(即,不try 向调用者返回任何内容),这就相当容易了,上面的代码提示了如何做到这一点:只需提取经过go 糖化处理的代码的公共部分:

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;

使用此更改,Example编译为-playground.

Rust相关问答推荐

使用 struct 外部的属性来改变 struct 的原始方式

MutexGuard中的过滤载体不需要克隆

在Rust中,在实现特征`Display`时,如何获取调用方指定的格式?

如何为 struct 字段设置新值并在Ruust中的可变方法中返回旧值

什么时候和为什么S最好是按值或引用传递简单类型

使用Py03从Rust调用Python函数时的最佳返回类型

有没有可能让泛型Rust T总是堆分配的?

通过RabbitMQ取消铁 rust 中长时间运行的人造丝任务的策略

如何修复&q;无法返回引用函数参数的值在异步规则中返回引用当前函数&q;拥有的数据的值?

rust 蚀生命周期 不匹配-不一定超过此处定义的生命周期

Rust wasm 中的 Closure::new 和 Closure::wrap 有什么区别

为什么`tokio::main`可以直接使用而不需要任何导入?

一次不能多次borrow *obj作为可变对象

为什么是&mut发送?线程如何在安全的 Rust 中捕获 &mut?

如何在 Rust 中将枚举变体转换为 u8?

如何在 Rust 中将 UTF-8 十六进制值转换为 char?

为什么我可以同时传递可变和不可变引用?

如何在 Rust 中返回通用 struct

Rust 中的运行时插件

如何阅读 HttpRequest 主体