The Mungler
The Mungler

Reputation: 166

How do I clone a variable before moving it into warp's .then() filter?

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

Answers (2)

Blue
Blue

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:

  1. Move the value out of the environment and into the closure object
  2. Move the value out of the closure and into the async block
  3. The closure returns the async block

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:

  1. Move the value into the closure
  2. Before creating a new Future, clone the value inside the closure
  3. Move the cloned value into the Future
  4. Return the Future

Hope this helps explain things!

Upvotes: 0

t56k
t56k

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

Related Questions