Wrangling with tricky lifetimes and closures [async, actix-web, sqlx]

Context

I'm using actix-web and sqlx to create a simple HTTP server with basic auth service.


Services

I have several services that look like this

#[derive(Clone)]
pub struct AuthFetchService<'service> {
    fetch_store: &'service (dyn Fetch<Auth, Arg = Arg> + Send + Sync)
}

impl<'service> AuthFetchService<'service> {
    pub async fn fetch(&self, arg: &Arg) -> ServiceResult<Auth> {
        self.fetch_store
            .fetch(arg)
            .await
            .map_err(ServiceError::from)
    }
}

where each ___store (here fetch_store) is a trait object that implements some sort of fetch function, which is just an SQL query result (sqlx) given some database pool that implements Send + Sync. Trait because I want to make it DB agnostic.


Aggregates

These services together perform an "aggregate" task, described as

#[derive(Clone)]
pub struct EntityAggregate<'entity> {
    auth_fetch: &'entity AuthFetchService<'entity>,
    token_create: &'entity TokenCreateService<'entity>,
    entity_fetch: &'entity EntityFetchService<'entity>,
    ...
    ...
}

impl<'entity> EntityAggregate<'entity> {
    pub async fn fetch(&self, token: String, arg: Arg) -> AggregateResult<Entity> {
    // Check token's validity.
    // Make sure token still exists in db.
    // Make sure token has enough privileges to 'fetch' this entity.
    // Make sure requested entity exists.
    // etc.
    }
}

The idea is for services to do one single task and aggregates to basically orchestrate different services to complete a full task given by the user.

Main

Concretely constructed as

let store = Postgres::new()
    .await
    .expect("Failed to initialize `Postgres` store");

let auth_fetch = AuthFetchService::new(&store);
let token_create = TokenCreateService::new(&store);
let entity_fetch = EntityFetchService::new(&store);
...
...

let entity_aggregate = Arc::new(EntityAggregate::new(
    &auth_fetch,
    &token_create,
    &entity_fetch,
    ...
    ...
    );

HttpServer::new(move || {
        App::new()
            .app_data(web::JsonConfig::default().limit(4096))
            .app_data(Data::from(entity_aggregate.clone()))
            .service(
                web::scope("/api/v1")
                    .service(route::entity::fetch)
            )
    })
    .bind(("0.0.0.0", 8080))?
    .run()
    .await

Problem

   --> src/main.rs:66:44
    |
66  |let auth_fetch = AuthFetchService::new(&store);
    |                                       ^^^^^^
    |                                       |
    |                     borrowed value does not live long enough
    |                    cast requires that `store` is borrowed for `'static`
...
192 | }
    | - `store` dropped here while still borrowed

error[E0597]: `auth_fetch` does not live long enough
   --> src/main.rs:98:9
    |
98  |           &auth_fetch,
    |           ^^^^^^^^^^^ borrowed value does not live long enough
...
143 | /         App::new()
144 | |             .wrap(TracingLogger::default())
...   |
163 | |             .app_data(Data::from(entity_aggregate.clone()))
    | |___________________________________________________________
argument requires that `auth_fetch` is borrowed for `'static`
...
192 |   }
    |   - `auth_fetch` dropped here while still borrowed

(line 192 is end of main fn)

I understand app_data requires 'static lifetime which I thought maybe putting aggregrator in Arc and then cloning inside the closure might satisfy. But sadly it doesn't.

So my question is, would it be possible to make this setup work without wrapping references in Rc or perhaps Arc and generous use of .clone() :sweat_smile:

No, that can't possibly be the case. Just think about it. If a value of type T: 'a is only valid for a lifetime 'a, then that means it's (potentially) not valid to hold on to for any longer (i.e., for longer than 'a). How could an Arc change this? The Arc doesn't magically own (and extend the lifetime of) whatever your value borrows from; it can only affect the ownership of the value that it directly wraps.

If app_data requires the 'static lifetime bound, then there is no possible way you can satisfy that using a value borrowed from a local variable. You'll have to give it something owned.