无法在异步 `Fn` 闭包中移出已捕获的变量。

huangapple go评论59阅读模式
英文:

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

问题

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

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

        // 在打开新连接时将连接添加到客户端
        connections.lock().await.insert(uuid, stream.clone());

        // 等待连接关闭
        while let Some(Ok(_)) = stream.next().await {}

        // 当连接关闭时,从客户端中删除连接
        connections.lock().await.remove(&uuid);

        Ok(())
    }));

    // app.bind(url).await
}

当我尝试编译这个程序时,rustc 报错如下:

error[E0507]: 无法从`connections`中移出一个在`Fn`闭包中被捕获的变量
  --> src/main.rs:11:57
   |
9  |       let connections = Arc::new(Mutex::new(HashMap::new()));
   |           ----------- 被外部捕获的变量
10 |       let mut app = tide::new();
11 |       app.at("/").get(WebSocket::new(move |_, mut stream| async move {
   |  ____________________________________--------------------^
   | |                                    |
   | |                                    被此`Fn`闭包捕获
12 | |         let uuid = Uuid::new_v4();
13 | |
14 | |         // 在打开新连接时将连接添加到客户端
15 | |         connections.lock().await.insert(uuid, stream.clone());
   | |         -----------
   | |         |
   | |         变量由于在生成器中的使用而被移出
   | |         移出发生在`connections`有类型`Arc<async_std::sync::Mutex<HashMap<Uuid, WebSocketConnection>>>`,它不实现`Copy`特性
...  |
23 | |         Ok(())
24 | |     }));
   | |_____^ 移出`connections`发生在此处

有关此错误的更多信息,请尝试运行 `rustc --explain E0507`。
error: 由于先前的错误,无法编译`mre`

以下是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,
{
    /// 用一个处理函数构建一个新的WebSocket
    pub fn new(handler: H) -> Self {
        Self {
            handler: Arc::new(handler),
            ghostly_apparition: PhantomData,
            protocols: Default::default(),
        }
    }

    // ...
}

在发布这个问题之前,我尝试搜索了这个问题。大多数答案要么与问题无关,要么需要修改Websocket::new方法的源代码。但这个方法不是我自己写的,而是来自第三方库。是否还有解决这个问题的方法?

英文:

Here is my code. In this program, I want to create a simple websocket server. When user sends a request to the ws://{url}/, the browser will establish a websocket connection with the server.

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(&quot;/&quot;).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(&amp;uuid);

        Ok(())
    }));

    // app.bind(url).await
}

When I tried to compile this program, the rustc said:

error[E0507]: cannot move out of `connections`, a captured variable in an `Fn` closure
  --&gt; 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(&quot;/&quot;).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&lt;async_std::sync::Mutex&lt;HashMap&lt;Uuid, WebSocketConnection&gt;&gt;&gt;`, 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

And this is the definition of the Websocket::new method (no sure if it's useful):

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

    // ...
}

I tried searching this problem before posting this question. Most of the answers are either irrelevant, or need to modify the source code of the method (Websocket::new method here). But this method is not written by me but is from a third-party crate. Is there still any way to resolve this problem?

答案1

得分: 1

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

但是,在你的代码中,它在一个async move内部使用了connections变量,这意味着它将该变量移动到了async块内部。出于明显的原因,这只能做一次。

不过,修复很容易。不要移动整个connections变量,而是需要创建一个新的Arc引用来引用connections变量,并将其移动到async move中。这样,每次调用都会获得它自己的副本,使其与Fn兼容。

以下是一个编译通过的版本:

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

            // 在打开新连接时将连接添加到客户端
            connections.lock().await.insert(uuid, stream.clone());

            // 等待连接关闭
            while let Some(Ok(_)) = stream.next().await {}

            // 在连接关闭时从客户端中删除连接
            connections.lock().await.remove(&uuid);

            Ok(())
        }
    }));

    // app.bind(url).await
}

希望这可以帮助你解决问题。

英文:

The argument of WebSocket::new() has to be an Fn closure, meaning, it must be callable repeatedly.

In your code, however, it internally uses the connections variable inside of an async move, meaning it moves the variable into the async block. This can for obvious reasons only be done once.

It's easy to fix, though. Instead of moving the entire connections variable in, you need to create a new Arc reference of the connections variable and move that one into the async move. So every invocation gets its own copy of it, making it compatible with Fn.

Here is a compiling version:

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(&quot;/&quot;).get(WebSocket::new(move |_, mut stream| {
        let connections = Arc::clone(&amp;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(&amp;uuid);

            Ok(())
        }
    }));

    // app.bind(url).await
}

huangapple
  • 本文由 发表于 2023年2月8日 20:11:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/75385614.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定