【问题标题】:expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce` in actix web期望一个实现 `Fn` 特征的闭包,但这个闭包仅在 actix web 中实现 `FnOnce`
【发布时间】:2022-01-04 22:09:00
【问题描述】:

我正在使用 actix web,我试图在闭包中返回一个异步函数,但我收到以下错误:



error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
   --> src/server.rs:134:33
    |
133 |    ...                   web::get().to(
    |                                     -- the requirement to implement `Fn` derives from here
134 |  / ...                       move |router: web::Data<Arc<Router>>,
135 |  | ...                             headers: web::Data<Arc<Headers>>,
136 |  | ...                             stream: web::Payload,
137 |  | ...                             req: HttpRequest| async {
    |  |_________________________________________________________-
138 | || ...                           start_web_socket(req, stream, params).await
139 | || ...                       },
    | ||                           ^
    | ||___________________________|
    | |____________________________this closure implements `FnOnce`, not `Fn`
    |                              closure is `FnOnce` because it moves the variable `params` out of its environment


error: aborting due to previous error; 1 warning emitted

这是导致错误的 sn-p。我已尝试尽可能多地记录代码。

我尝试将变量移入和移出移动块,并尝试将它们放置在不同的地方,但没有成功。

应该做些什么来代替这个?

    pub fn start(
        &mut self,
        py: Python,
        url: String,
        port: u16,
        socket: &PyCell<SocketHeld>,
        name: String,
        workers: usize,
    ) -> PyResult<()> {
        if STARTED
            .compare_exchange(false, true, SeqCst, Relaxed)
            .is_err()
        {
            println!("Already running...");
            return Ok(());
        }

        println!("{}", name);

        let borrow = socket.try_borrow_mut()?;
        let held_socket: &SocketHeld = &*borrow;

        let raw_socket = held_socket.get_socket();
        println!("Got our socket {:?}", raw_socket);

        let router = self.router.clone();
        let headers = self.headers.clone();
        let directories = self.directories.clone();
        let workers = Arc::new(workers);

        let asyncio = py.import("asyncio").unwrap();

        let event_loop = asyncio.call_method0("new_event_loop").unwrap();
        asyncio
            .call_method1("set_event_loop", (event_loop,))
            .unwrap();
        let event_loop_hdl = PyObject::from(event_loop);

        thread::spawn(move || {
            //init_current_thread_once();
            actix_web::rt::System::new().block_on(async move {
                let addr = format!("{}:{}", url, port);

                println!("The number of workers are {}", workers.clone());

                HttpServer::new(move || {
                    let mut app = App::new();
                    let event_loop_hdl = event_loop_hdl.clone();
                    let directories = directories.read().unwrap();
                    let router_copy = router.clone();

                    // this loop matches three types of directory serving
                    // 1. Serves a build folder. e.g. the build folder generated from yarn build
                    // 2. Shows file listing
                    // 3. Just serves the file without any redirection to sub links
                    for directory in directories.iter() {
                        if let Some(index_file) = &directory.index_file {
                            app = app.service(
                                Files::new(&directory.route, &directory.directory_path)
                                    .index_file(index_file)
                                    .redirect_to_slash_directory(),
                            );
                        } else if directory.show_files_listing {
                            app = app.service(
                                Files::new(&directory.route, &directory.directory_path)
                                    .redirect_to_slash_directory()
                                    .show_files_listing(),
                            );
                        } else {
                            app = app
                                .service(Files::new(&directory.route, &directory.directory_path));
                        }
                    }

                    app = app
                        .app_data(web::Data::new(router.clone()))
                        .app_data(web::Data::new(headers.clone()));

                    let web_socket_map = router_copy.get_web_socket_map().unwrap();
                    for elem in (web_socket_map).iter() {
                        let route = elem.key().clone();
                        let params = elem.value().clone();
                        app = app.route(
                            &route,
                            web::get().to(
                                move |router: web::Data<Arc<Router>>,
                                      headers: web::Data<Arc<Headers>>,
                                      stream: web::Payload,
                                      req: HttpRequest| async {
                                    start_web_socket(req, stream, params).await
                                },
                            ),
                        )
                    }

                    app.default_service(web::route().to(move |router, headers, payload, req| {
                        pyo3_asyncio::tokio::scope_local(event_loop_hdl.clone(), async move {
                            index(router, headers, payload, req).await
                        })
                    }))
                })
                .keep_alive(KeepAlive::Os)
                .workers(*workers.clone())
                .client_timeout(0)
                .listen(raw_socket.try_into().unwrap())
                .unwrap()
                .run()
                .await
                .unwrap();
            });
        });

----更新----

感谢您的建议。在更新 params 时,我能够摆脱原来的错误,但我得到了一个类似但新的错误:

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
   --> src/server.rs:134:33
    |
133 |    ...                   web::get().to(
    |                                     -- the requirement to implement `Fn` derives from here
134 |  / ...                       |router: web::Data<Arc<Router>>,
135 |  | ...                        headers: web::Data<Arc<Headers>>,
136 |  | ...                        stream: web::Payload,
137 |  | ...                        req: HttpRequest| async move {
    |  |_________________________________________________________-
138 | || ...                           start_web_socket(req, stream, params.clone()).await
139 | || ...                       },
    | ||                           ^
    | ||___________________________|
    | |____________________________this closure implements `FnOnce`, not `Fn`
    |                              closure is `FnOnce` because it moves the variable `params` out of its environment

新的 sn-p 如下所示:

                    let web_socket_map = router_copy.get_web_socket_map().unwrap();
                    for elem in (web_socket_map).iter() {
                        let route = elem.key().clone();
                        let params = elem.value().clone();
                        app = app.route(
                            &route,
                            web::get().to(
                                |router: web::Data<Arc<Router>>,
                                 headers: web::Data<Arc<Headers>>,
                                 stream: web::Payload,
                                 req: HttpRequest| async move {
                                    start_web_socket(req, stream, params.clone()).await
                                },
                            ),
                        )
                    }

我已经尝试在 async move 中克隆参数,但它仍然给出相同的错误。

【问题讨论】:

  • 我认为stackoverflow.com/questions/30177395/… 可能会有所帮助;虽然我不是很精通 Rust,但我猜这里使用move 意味着这个闭包不能实现Fn
  • @msbit ,我已经看到了这个答案。但是这个答案并没有告诉我可以在这里进行的任何更改。
  • 不,这是更一般的信息。
  • 最后,在start_web_socket 中,尝试将params 替换为elem.value().clone()
  • @msbit move doesn't 表示闭包无法实现Fnmove 导致环境中的值归闭包所有。调用闭包时消耗的值并不适用,这就是FnOnce 的意思。

标签: rust actix-web


【解决方案1】:

web::get() 返回一个 Route,而 web::get().to(...) 是一个需要 Handler 的 Route 方法。 Handler 应该是一个异步函数 (async fn) - 一个在调用时返回 Future 的函数。

问题是在你的代码中你传递了一个异步块,这是一个未来。

异步块是块表达式的变体,其计算结果为未来。

所以你的代码:

async move |...| { // Future
    start_web_socket(req, stream, params.clone()).await
}

可以转换为:

move |...| { // Handler Fn
    async move { // Future
        start_web_socket(req, stream, params.clone()).await
    }
}

这相当于:

move |...| { // Handler Fn
    start_web_socket(req, stream, params.clone()) // Future
}

因为当您调用 async fn start_web_socket 而没有 .await 时,您会得到一个 Future。

调试此类事物的一个技巧是将事物分配给中间变量,并检查编译器为它们推导的类型。

【讨论】:

  • 感谢您的精彩解释。我终于可以理解 move 和 async move 之间的区别了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-14
  • 2015-07-22
  • 2022-09-24
  • 1970-01-01
  • 2021-08-20
相关资源
最近更新 更多