Reputation: 166
I have the following code snippet:
async fn server(config: crate::Config) {
println!("Building server");
let key = hmac::Key::new(hmac::HMAC_SHA256, config.docusign.hmac_key.as_bytes());
let webhook = warp::path("webhook")
.and(warp::post())
.and(warp::body::content_length_limit(4194304))
.and(warp::header::headers_cloned())
.and(warp::body::bytes())
.then(|headers: HeaderMap, bytes: Bytes| async move {
match verify_msg(&key, &headers, &bytes) {
Ok(_) => {
println!("Message is Valid!");
process_msg(bytes).await.into_response()
}
Err(string) => {
println!("{string}");
warp::reply::with_status(warp::reply(), http::StatusCode::UNAUTHORIZED)
.into_response()
}
}
});
warp::serve(webhook)
.tls()
.cert_path("cert/cert.pem")
.key_path("cert/key.pem")
.run(([0, 0, 0, 0], 443))
.await;
println!("Shutting down Server");
}
This gives me an error:
expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
this closure implements `FnOnce`, not `Fn`rustc(E0525)
server.rs(20, 4): the requirement to implement `Fn` derives from here
server.rs(20, 9): this closure implements `FnOnce`, not `Fn`
server.rs(21, 22): closure is `FnOnce` because it moves the variable `key` out of its environment
This makes sense, I am using the key variable and thus moving it out of the environment. What I can't figure out is how can I get this async closure to work without moving the key? I've tried cloning it like this: match verify_msg(&key.clone(), &headers, &bytes)
but it still doesn't work. I guess that makes sense, since the variable is still referenced inside the closure. So, how do I clone the key before it gets moved?
I was able to get it working with .map() and a regular (non-async) closure, but the process_msg() function is async, so I don't think that would work.
Edit: The answer from @t56k got me on the right track, but didn't quite work. Going in the direction of putting async blocks inside of a closure and following the compiler's recommendations eventually got me this:
async fn server(config: crate::Config) {
println!("Building server");
let key = hmac::Key::new(hmac::HMAC_SHA256, config.docusign.hmac_key.as_bytes());
let webhook = warp::path("webhook")
.and(warp::post())
.and(warp::body::content_length_limit(4194304))
.and(warp::header::headers_cloned())
.and(warp::body::bytes())
.then(move |headers: HeaderMap, bytes: Bytes| {
let key = key.clone();
async move {
match verify_msg(&key, &headers, &bytes) {
Ok(_) => {
println!("Message is Valid!");
process_msg(bytes).await.into_response()
}
Err(string) => {
println!("{string}");
warp::reply::with_status(warp::reply(), http::StatusCode::UNAUTHORIZED)
.into_response()
}
}
}
});
warp::serve(webhook)
.tls()
.cert_path("cert/cert.pem")
.key_path("cert/key.pem")
.run(([0, 0, 0, 0], 443))
.await;
println!("Shutting down Server");
}
which works perfectly for some reason even though I'm using the move
keyword. I guess i'm only allowed to move key
if it isn't inside of an async
block? In any case, my problem is solved, but if anyone could explain why this works I would gladly accept it.
Upvotes: 1
Views: 522
Reputation: 557
(Friend got directed here when googling a similar issue, thought I'd provide context for future searchers too)
The main issue here is Rust's lack of support for "true" async closures.
When you move a value into a move || {...}
, that value is now owned by the closure object. If calling the closure only needs references to that value, then it can be Fn
.
Similarly, when you move a value into an async move {...}
, that value becomes a part of the Future object, stored as part of the state when the Future is polled.
The trouble arises when you try to do move || async move {...}
. What this does is:
Moves can only happen once, so the closure can only be called once, because the closure is returning a different Future every time it's called. This is what I meant by there aren't yet true async closures; a true async closure, async move || {...}
would construct a single Future the way a closure is just a single object.
The workaround you need to do (and ended up doing) is to:
Hope this helps explain things!
Upvotes: 0
Reputation: 6981
This is untested for your use case, but you can .clone()
things before the move
to allow them access.
.and_then(|headers: HeaderMap, bytes: Bytes| async {
let key = key.clone();
move {
match verify_msg(key, &headers, &bytes) {
// ...
}
}
});
Upvotes: 3