Search code examples
rustrust-tokiohyper

Sharing data structures across async closures


I'm stuck trying to share (read-only) data structures across async helpers. What I'm trying to accomplish is create a Hyper server where I pre-generate some data that can be used by all request handlers.

Here is the example from the Hyper getting started guide, extended with what I'm trying to do:

#[tokio::main]
async fn main() {
    let address = SocketAddr::from(([127, 0, 0, 1], 3000));

    let pages = generate_static_pages();

    let make_service = make_service_fn(|_conn| async move {
        Ok::<_, Infallible>(service_fn(|req: Request<Body>| async move {
            serve(pages, req)
        }))
    });

    let server = Server::bind(&address).serve(make_service);

    if let Err(error) = server.await {
        eprintln!("server error: {}", error);
    }
}

In my case, generate_static_pages() returns a HashMap<&'static str, Bytes> with pre-generated pages. Unfortunately, this hash map cannot be generated at compile-time, because that would make things a lot easier. Now, I struggle because pages cannot be borrowed by the closures: "cannot move out of pages, a captured variable in an FnMut closure"

I tried to pass a reference, but that didn't work because Rust cannot infer the variable to live long enough for the closure to use. I then tried to use .clone() but that doesn't work because it would be called on the variable after it is moved, which it can't. Finally, I tried wrapping in an Arc, but that doesn't solve it, basically because of the same reason.

What would you advice me to do? Thanks!


Solution

  • If you only want immutable references to the pages then you should be able to use the lazy_static crate. lazy_static lets you initialize static variables at runtime - it's quite useful!

    Your code would end up looking something like:

    use lazy_static::lazy_static;
    
    lazy_static! {
      static ref PAGES: HashMap<&'static str, Bytes> = generate_static_pages();
    }
    
    #[tokio::main]
    async fn main() {
        let address = SocketAddr::from(([127, 0, 0, 1], 3000));
    
        let make_service = make_service_fn(|_conn| async move {
            Ok::<_, Infallible>(service_fn(|req: Request<Body>| async move {
                serve(&PAGES, req)
            }))
        });
    
        let server = Server::bind(&address).serve(make_service);
    
        if let Err(error) = server.await {
            eprintln!("server error: {}", error);
        }
    }
    

    Also, here's another lazy_static example.