Dependency Injetions by passing parameters with futures, actix and sqlx

I'm trying to do some basic dependency injection by passing functions as arguments and I'm hitting a road block. I'm using actix and sqlx and here is some of my code.

#[derive(Deserialize, sqlx::FromRow)]
pub struct Item {
    pub id: Uuid,
    pub name: String
}

#[derive(Deserialize)]
pub struct ItemForm {
    pub name: String
}

pub async fn sqlx_find_by_name(pool: &PgPool, name: &String) -> Option<Item> {
    sqlx::query_as!(
        Item, 
        "select * from items where name = $1",
        &name
    ).fetch_optional(pool).await.unwrap()
}

pub async fn sqlx_insert(pool: &PgPool, item: &Item) -> () {
    sqlx::query!(
        "insert into items (id, name) values ($1, $2)",
        &item.id,
        &item.name
    )
    .execute(pool).await.unwrap();
    ()
}


pub async fn create_item<FA,FB>(find_by_name: impl FnOnce(&PgPool, &String) -> FA,
                                insert: impl FnOnce(&PgPool, &Item) -> FB,
                                pool: &PgPool,
                                item: &Item) -> () 
where FA: Future<Output = Option<Item>>,
      FB: Future<Output = ()>
{ 
    let may_user = find_by_name(&pool, &item.name).await;
    match may_user {
        Some(_) => (),
        None => {
            insert(&pool, &item).await
        }
    }
}

pub async fn create_item_handler(item_form: ItemForm, pool: PgPool) -> impl Responder {
    let item = Item { id: Uuid::new_v4(), name: item_form.name };
    let _ = create_item(
        sqlx_find_by_name,
        sqlx_insert,
        &pool,
        &item
    ).await;
    HttpResponse::Ok().body("All Good")
}

I'm getting a compile error in the handler that states

error[E0308]: mismatched types
   --> src/main.rs:143:13
    |
143 |     let _ = create_item(
    |             ^^^^^^^^^^^ lifetime mismatch
    |
    = note: expected associated type `<for<'_, '_> fn(&Pool<Postgres>, &std::string::String) -> impl std::future::Future {sqlx_find_by_name} as FnOnce<(&Pool<Postgres>, &std::string::String)>>::Output`
               found associated type `<for<'_, '_> fn(&Pool<Postgres>, &std::string::String) -> impl std::future::Future {sqlx_find_by_name} as FnOnce<(&Pool<Postgres>, &std::string::String)>>::Output`
    = note: the required lifetime does not necessarily outlive the empty lifetime
note: the lifetime requirement is introduced here
   --> src/main.rs:125:80
    |
125 | pub async fn create_item<FA,FB>(find_by_name: impl FnOnce(&PgPool, &String) -> FA,
    |                                                                                ^^

I'm not entirely sure what to do next from here. Thanks!

Edit

After getting extra errors with cargo build that was not showing up in the IDE, I went back to fix those and updated the code section to reflect those changes. I also replaced the IDE compiler error with the cargo compiler error.

When you post errors, please post them with the formatting that cargo produces. The stuff you get from the IDE popups is often pretty useless.

Ok I'll remember that. Also cargo build is showing me compile errors that the ide isn't displaying so I may need to resolve those first.

Ok I resolved the extra compile errors and I'm still getting this specific error. I also updated the error in my original post to show the output of cargo.

If it helps, I've created a playground that mimics this just without the libraries.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=956025cd9647b4aa58dcab58090b7f85

The problem is that the futures generated by async/await will borrow from the inputs when you pass them references, but your signature does not allow this. If you change it to not take references, it will work.

Thanks for your help. I've updated the code to not take references and used clones.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b6cf58771e625af6440ceacb683a76f
So just clone everything? Is this idiomatic in rust?

Well, cloning when you have to is perfectly fine.

Do you even need the closures to be async?

Yeah just general app dev good practices (according to me so may not be valid). All calls to an outside data source should be async. All side effects should be injected into functions with business logic so it can be fully unit tested. In Scala it would also mean they would be closures returning a Future of something, In Haskell it would be the IO monad or some variant. The function I'm asking about was stripped down for the question but I will also be adding a ton of validation and error handling in there. This is similar to the Repository Service pattern used in Spring (and .net?), but in a more functional way. Thanks again for your help!

Right, the question about async was obscured by your simplified playground. You do need it in the original code.

Anyway, for the specific function you posted, I don't really see the point in using this pattern. Just call find_by_name and insert directly in create_item_handler, or define a function that hard-codes the sqlx_find_by_name and sqlx_insert methods. Avoiding the pattern avoids the requirement to clone.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.