为什么通过移动捕获Arc会使我的闭包FnOnce不再是Fn?

11
在下面的示例中,我正在使用一个Arc来从请求处理程序引用服务器状态,但编译器使闭包成为了FnOnce。感觉我正在做正确的事情,因为每个闭包都拥有对状态的强引用。为什么这不起作用?有哪些选项可以使其工作?其他类似Share Arc between closures的问题表明这样的内容是可行的,但我正在像所示的那样进行每个闭包副本的克隆,并且仍然遇到错误。
#![feature(async_closure)]

#[derive(Default, Debug)]
struct State {}

impl State {
    pub async fn exists(&self, key: &str) -> bool {
        true
    }

    pub async fn update(&self, key: &str) {}
}

#[tokio::main]
async fn main() {
    use warp::Filter;
    use std::sync::Arc;

    let state: Arc<State> = Arc::default();

    let api = warp::post()
        .and(warp::path("/api"))
        .and(warp::path::param::<String>().and_then({
            let state = Arc::clone(&state);
            async move |p: String| {
                let x = state.exists(&p);
                if x.await {
                    Ok(p)
                } else {
                    Err(warp::reject::not_found())
                }
            }
        }))
        .and_then({
            let state = Arc::clone(&state);
            async move |id: String| {
                state.update(&id).await;
                Result::<String, warp::Rejection>::Ok("".to_owned())
            }
        });

    warp::serve(api).run(([127, 0, 0, 1], 0)).await;
}
error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
  --> src/main.rs:25:13
   |
23 |           .and(warp::path::param::<String>().and_then({
   |                                              -------- the requirement to implement `Fn` derives from here
24 |               let state = Arc::clone(&state);
25 |               async move |p: String| {
   |  _____________^^^^^^^^^^^^^^^^^^^^^^_-
   | |             |
   | |             this closure implements `FnOnce`, not `Fn`
26 | |                 let x = state.exists(&p);
27 | |                 if x.await {
28 | |                     Ok(p)
...  |
31 | |                 }
32 | |             }
   | |_____________- closure is `FnOnce` because it moves the variable `state` out of its environment
1个回答

9

好的,异步闭包目前还不稳定,所以可能存在漏洞。我认为当异步块捕获 Arc 后,它会消耗掉它,因此该闭包实际上只能调用一次。我的意思是,异步闭包是某种生成器:每次调用它都会构造一个 future,而正是 future 保留了被捕获的值。

作为解决方法,您可以编写类似以下代码:

let state = Arc::clone(&state);
move |p: String| {
    let state = Arc::clone(&state);
    async move {
        let x = state.exists(&p);
        //...
    }
}

async 块之前,在闭包内部进行另一个克隆操作,可以确保您可以根据需要多次调用闭包。

我认为实际上 Warp::Filter 应该一开始就接受一个 FnOnce,但我不太了解 warp,无法确定。


我认为实际上Warp ::Filter应该首先接受一个FnOnce,但我不了解warp足够确定。过滤器会为每个匹配的请求调用,因此据我理解,FnOnce可能不够。 - devnev
@devnev:啊,因为你正在构建一个服务器,当然了!我看到了“post”,以为是客户端代码。 - rodrigo

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