Cached value in rouille closure

I'm using rouille as a web server and I would like to cache a value across requests.
In my real application I'm getting the error message client has an anonymous lifetime '_ but it needs to satisfy a 'static lifetime requirement but I made a minimal example which also fails to compile:

use std::sync::Mutex;
use rouille::Response;
use std::cell::Cell;

struct Client {
    cache: Mutex<Cell<String>>
}

fn main() {
    let client = Client {
        cache: Mutex::new(Cell::new(String::new())),
    };

    start_server(&client);
}

fn start_server(client: &Client) {
    rouille::start_server("127.0.0.1", move |request| {
        let cached_value = cached_value(client);
        Response::text(format!("Cached value is: {}", cached_value))
    });
}

fn cached_value(client: &Client) -> String {
    let cell = client.cache.lock().unwrap();
    let mut cache_value = cell.take();
    if cache_value == "" {
        cache_value = String::from("foo");
    }
    cell.set(cache_value);

    cache_value.clone()
}

The error is a little bit different here, but along the same lines:

error[E0621]: explicit lifetime required in the type of `client`
  --> src\main.rs:18:5
   |
17 | fn start_server(client: &Client) {
   |                         ------- help: add explicit lifetime `'static` to the type of `client`: `&'static Client`
18 |     rouille::start_server("127.0.0.1", move |request| {
   |     ^^^^^^^^^^^^^^^^^^^^^ lifetime `'static` required

Simply applying the hint is not enough.

This is because anything used inside the move |request| { ... } closure must be 'static, which your &Client is not. You have two options:

  1. Use Box::leak to obtain a &'static Client.
  2. Use Arc and share it with Arc<Client> rather than &Client.

Also, it doesn't make any sense to put a Cell in a Mutex.

1 Like

Ok, I'd like to try this, even if only to improve my skills.

I would have preferred a solution that is implemented solely inside Client, but I'll try the "wrapping in an Arc" solution. So instead of simply creating a Client, I create Arc::new(Client {...}) and I change the function signatures to client: Arc<Client> instead of client: &Client. Then I get:

error[E0507]: cannot move out of `client`, a captured variable in an `Fn` closure
  --> src\main.rs:19:41
   |
17 | fn start_server(client: Arc<Client>) {
   |                 ------ captured outer variable
18 |     rouille::start_server("127.0.0.1", move |request| {
19 |         let cached_value = cached_value(client);
   |                                         ^^^^^^ move occurs because `client` has type `Arc<Client>`, which does not implement the `Copy` trait

My intention was that if multiple requests start with the cache unpopulated, only one of them actually populates it while the others block.

You are going to need to clone it when passing it to cached_value. Don't worry, cloning an Arc is cheap — that's the point of using an Arc. Another option is to make cached_value still take &Client, since through the same conversion that turns &String → &str, you can use &Arc<Client> as &Client when calling functions, and this is fine once you have transferred it inside the move |request| { ... } part.

The mutex already does this without the help of a Cell.