In the below example, I'm using an Arc to reference server state from a request handler, but the compiler make the closure an FnOnce
. It feels like I'm doing the right thing in that each closure owns a strong reference to the state. Why doesn't this work? What options are there to make it work? Other questions like Share Arc between closures indicate something like this working, but I'm making the per-closure clones as shown and still getting an error.
#![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
Well, async closures are unstable, so that may be a bug. I think that when the async block captures the Arc
it consumes it, so the closure can actually only be called once. I mean, an async closure is some kind of generator: each time you call it it constructs a future, and it is the future which keeps the captured values.
As a workaround you may write something like this:
let state = Arc::clone(&state);
move |p: String| {
let state = Arc::clone(&state);
async move {
let x = state.exists(&p);
//...
}
}
Doing another clone inside the closure but before the async
block ensures that you can call the closure as many times as you need.
I would argue that actually Warp::Filter
should accept a FnOnce
to begin with, but I don't know warp
enough to be sure.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With