无法移出异步“Fn”闭包中捕获的变量

cannot move out of a captured variable in an async `Fn` closure

提问人:Lomírus 提问时间:2/8/2023 最后编辑:Lomírus 更新时间:2/9/2023 访问量:239

问:

这是我的代码。在这个程序中,我想创建一个简单的 websocket 服务器。当用户向 发送请求时,浏览器将与服务器建立 websocket 连接。ws://{url}/

use std::{collections::HashMap, sync::Arc};

use async_std::{prelude::*, sync::Mutex};
use tide_websockets::WebSocket;
use uuid::Uuid;

#[async_std::main]
async fn main() {
    let connections = Arc::new(Mutex::new(HashMap::new()));
    let mut app = tide::new();
    app.at("/").get(WebSocket::new(move |_, mut stream| async move {
        let uuid = Uuid::new_v4();

        // Add the connection to clients when opening a new connection
        connections.lock().await.insert(uuid, stream.clone());

        // Waiting for the connection to be closed
        while let Some(Ok(_)) = stream.next().await {}

        // Remove the connection from clients when it is closed
        connections.lock().await.remove(&uuid);

        Ok(())
    }));

    // app.bind(url).await
}

当我尝试编译这个程序时,rustc 说:

error[E0507]: cannot move out of `connections`, a captured variable in an `Fn` closure
  --> src/main.rs:11:57
   |
9  |       let connections = Arc::new(Mutex::new(HashMap::new()));
   |           ----------- captured outer variable
10 |       let mut app = tide::new();
11 |       app.at("/").get(WebSocket::new(move |_, mut stream| async move {
   |  ____________________________________--------------------_^
   | |                                    |
   | |                                    captured by this `Fn` closure
12 | |         let uuid = Uuid::new_v4();
13 | |
14 | |         // Add the connection to clients when opening a new connection
15 | |         connections.lock().await.insert(uuid, stream.clone());
   | |         -----------
   | |         |
   | |         variable moved due to use in generator
   | |         move occurs because `connections` has type `Arc<async_std::sync::Mutex<HashMap<Uuid, WebSocketConnection>>>`, which does not implement the `Copy` trait
...  |
23 | |         Ok(())
24 | |     }));
   | |_____^ move out of `connections` occurs here

For more information about this error, try `rustc --explain E0507`.
error: could not compile `mre` due to previous error

这是该方法的定义(不确定它是否有用):Websocket::new

impl<S, H, Fut> WebSocket<S, H>
where
    S: Send + Sync + Clone + 'static,
    H: Fn(Request<S>, WebSocketConnection) -> Fut + Sync + Send + 'static,
    Fut: Future<Output = Result<()>> + Send + 'static,
{
    /// Build a new WebSocket with a handler function that
    pub fn new(handler: H) -> Self {
        Self {
            handler: Arc::new(handler),
            ghostly_apparition: PhantomData,
            protocols: Default::default(),
        }
    }

    // ...
}

在发布这个问题之前,我尝试搜索这个问题。大多数答案要么是无关紧要的,要么需要修改方法的源代码(这里是方法)。但是这个方法不是我写的,而是来自第三方的板条箱。还有什么方法可以解决这个问题吗?Websocket::new

异步 Rust WebSocket 闭包所有权

评论

0赞 Esdeseserdt 2/8/2023
示例中的哪一行与错误消息中的行匹配?70
2赞 Finomnis 2/8/2023
请提供一个最小的可重复示例。例如,变量甚至不存在于您的代码中。如果显示的代码实际上没有产生您声称的错误消息,我们将无法为您提供帮助。stream
0赞 Lomírus 2/9/2023
@Finomnis 很抱歉我的问题的描述和代码含糊不清。现在,我用一个最小的可重现示例重写了这个问题。谢谢!
0赞 Lomírus 2/9/2023
@Esdeseserdt 我已经用一个最小的可重现示例更新了代码,以使这个问题更加清晰。前一行 70 对应第 24 行,这是闭包的结尾}));

答:

1赞 Finomnis 2/9/2023 #1

的参数必须是闭包,这意味着它必须是可重复调用的。WebSocket::new()Fn

但是,在代码中,它在内部使用 a 中的变量,这意味着它将变量移动到块中。由于显而易见的原因,这只能执行一次。connectionsasync moveasync

不过,这很容易修复。您需要创建变量的新引用,并将该引用移动到 .因此,每个调用都会获得自己的副本,使其与 .connectionsArcconnectionsasync moveFn

下面是一个编译版本:

use std::{collections::HashMap, sync::Arc};

use async_std::{prelude::*, sync::Mutex};
use tide_websockets::WebSocket;
use uuid::Uuid;

#[async_std::main]
async fn main() {
    let connections = Arc::new(Mutex::new(HashMap::new()));
    let mut app = tide::new();
    app.at("/").get(WebSocket::new(move |_, mut stream| {
        let connections = Arc::clone(&connections);
        async move {
            let uuid = Uuid::new_v4();

            // Add the connection to clients when opening a new connection
            connections.lock().await.insert(uuid, stream.clone());

            // Waiting for the connection to be closed
            while let Some(Ok(_)) = stream.next().await {}

            // Remove the connection from clients when it is closed
            connections.lock().await.remove(&uuid);

            Ok(())
        }
    }));

    // app.bind(url).await
}