Cloning variable inside of an "async move" block

What i'm trying to do is to create a simple web-service via Hyper.
And i want to pass a Database structure to the router using make_service_fn.

Thus i've ran into the problem: i can't clone a variable inside of the async move block inside of a closure. But if i do it right before the async move block - everything is fine.
Can someone explain why is that since i have to move a variable to the closure explicitly by move but still running into the move out of ... occurs here error ?

Concrete example:

Code
use hyper::server::conn::AddrStream;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, Server, StatusCode};
use std::convert::Infallible;

type ResponseFuture = Result<Response<Body>, Infallible>;

async fn router(req: Request<Body>, _db: Database) -> ResponseFuture {
    let mut response = Response::new(Body::empty());
    match (req.method(), req.uri().path()) {
        (&Method::GET, "/hello") => {
            *response.body_mut() = Body::from("OH HI");
        }
        (&Method::GET, "/create") => {
            *response.status_mut() = StatusCode::NOT_FOUND;
            *response.body_mut() = Body::from("NOT IMPLEMENTED YET");
        }
        _ => {
            *response.status_mut() = StatusCode::NOT_FOUND;
        }
    }

    Ok(response)
}

#[derive(Clone)]
struct Database;

#[tokio::main]
async fn main() {
    let addr = ([127, 0, 0, 1], 3000).into();
    let user_collection = Database;

    let make_svc = make_service_fn(move |socket: &AddrStream| async move {
        let collec = user_collection.clone();
        Ok::<_, Infallible>(service_fn(move |req: Request<Body>| async move {
            let coll = collec.clone();
            Ok::<_, Infallible>(match router(req, coll).await {
                Ok(val) => val,
                Err(_e) => Response::new(Body::empty()),
            })
        }))
    });

    let server = Server::bind(&addr).serve(make_svc);

    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}
Errors
error[E0507]: cannot move out of `collec`, a captured variable in an `FnMut` closure
  --> src/main.rs:53:77
   |
52 |           let collec = user_collection;
   |               ------ captured outer variable
53 |           Ok::<_, Infallible>(service_fn(move |req: Request<Body>| async move {
   |  _____________________________________________________________________________^
54 | |             let coll = collec.clone();
   | |                        ------
   | |                        |
   | |                        move occurs because `collec` has type `Database`, which does not implement the `Copy` trait
   | |                        move occurs due to use in generator
55 | |             Ok::<_, Infallible>(match router(req, coll).await {
56 | |                 Ok(val) => val,
57 | |                 Err(_e) => Response::new(Body::empty()),
58 | |             })
59 | |         }))
   | |_________^ move out of `collec` occurs here

error[E0507]: cannot move out of `user_collection`, a captured variable in an `FnMut` closure
  --> src/main.rs:51:74
   |
49 |       let user_collection = Database;
   |           --------------- captured outer variable
50 | 
51 |       let make_svc = make_service_fn(move |socket: &AddrStream| async move {
   |  __________________________________________________________________________^
52 | |         let collec = user_collection;
   | |                      ---------------
   | |                      |
   | |                      move occurs because `user_collection` has type `Database`, which does not implement the `Copy` trait
   | |                      move occurs due to use in generator
53 | |         Ok::<_, Infallible>(service_fn(move |req: Request<Body>| async move {
54 | |             let coll = collec.clone();
...  |
59 | |         }))
60 | |     });
   | |_____^ move out of `user_collection` occurs here

You need to clone them in the closures, not in the async blocks that the closures return:

let make_svc = make_service_fn(move |socket: &AddrStream| {
    let collec = user_collection.clone();
    async move {
        Ok::<_, Infallible>(service_fn(move |req: Request<Body>| {
            let coll = collec.clone();
            async move {
                Ok::<_, Infallible>(match router(req, coll).await {
                    Ok(val) => val,
                    Err(_e) => Response::new(Body::empty()),
                })
            }
        }))
    }
});

This issue is that the closures might be called more than once: The outer is called for each connection, and the inner is called for each request on that connection. This means that the closures cannot move the value they own into the async block, because then they wouldn't have another value for the next time it's called.

1 Like

async move block is just a future that will be evaluated at some point, so it has to already have everything it needs to work.

The problem is actually with closures that return async blocks, not async blocks themselves. The closure that is passed directly to make_service_fn() has a task to create another closure. As the outer closure may produce inner closure many times, inner closure cannot move variables from it as they may be needed more than once.

So as @alice wrote, making clones outside of futures will solve the problem.

One thing Rust could do better here is to mark with arrows closures, not async blocks.

1 Like

Thanks a lot! Much appreciated! That's exactly what i thought! I just wasn't sure if my thoughts were correct.

But if i do it right before the async move block - everything is fine.

Thanks a lot! Much appreciated!