How to pass async closure with references in the argument?

I am trying to write some code like below. The idea here is that the execute_transaction method will take an async closure as argument. The async closure takes two arguments - one id shared reference to AppDatabase itself and another is exclusive reference to DatabaseSession. The execute_transaction method creates the DatabaseSession object. Pass it on to the closure. Once the closure returns it either commits or aborts transaction. So that way the DatabaseSession is created and destroyed within execute_transaction method. Below is the code I came up with but it giving me following error.

#[tokio::main]
async fn main() {
    println!("Hello, world!");
    let db = AppDatabase(0);
    let r = db
        .execute_transaction(|db, session| async {
            let val = 5;
            db.insert_with_session(val, session).await
        })
        .await;
    println!("{:?}", r);
}

struct AppDatabase(i32);
struct DatabaseSession(i32);

impl AppDatabase {
    async fn execute_transaction<'a, 'b, F, Fut>(&'a self, f: F) -> anyhow::Result<()>
    where
        F: Fn(&'a AppDatabase, &'b mut DatabaseSession) -> Fut,
        Fut: std::future::Future<Output = anyhow::Result<()>> + 'a,
        'a: 'b,
    {
        let mut session = DatabaseSession(0);
        let result = f(&self, &mut session).await;
        if result.is_err() {
            // session.abort();
            println!("rollback transaction here");
        } else {
            // session.commit();
            println!("commit transaction here");
        }
        Ok(())
    }

    async fn insert_with_session(
        &self,
        val: i32,
        session: &mut DatabaseSession,
    ) -> anyhow::Result<()> {
        println!("some dummy insert operation here");
        session.0 = val;
        Ok(())
    }
}

error[E0597]: `session` does not live long enough
  --> src/main.rs:25:31
   |
18 |     async fn execute_transaction<'a, 'b, F, Fut>(&'a self, f: F) -> anyhow::Result<()>
   |                                      -- lifetime `'b` defined here
...
25 |         let result = f(&self, &mut session).await;
   |                      ---------^^^^^^^^^^^^-
   |                      |        |
   |                      |        borrowed value does not live long enough
   |                      argument requires that `session` is borrowed for `'b`
...
32 |     }
   |     - `session` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `async-callback` due to previous error

session variable gets dropped at line 32. Which is what I want. But why compiler thinks that it is still being borrowed beyond that point?
What would be the correct way to implement my scenario here?

Box::pin or other tricks to express the lifetime connections. Also see How to express that the Future returned by a closure lives only as long as its argument?


I've added the code here as an example of my crate async_closure, but don't recommend using it.

// call it
let r = db
    .execute_transaction(
        cb!({}; async |db: &AppDatabase, session: &mut DatabaseSession| -> Result<()> {
            let val = 5;
            db.insert_with_session(val, session).await
        }),
    )
    .await;

Thanks for tip. I think I will use the BoxFuture approach. Here is my modified code -

use futures::future::{BoxFuture, FutureExt};

#[tokio::main]
async fn main() {
    println!("Hello, world!");
    let db = AppDatabase(0);
    let r = db.execute_transaction(transaction).await;
    println!("{:?}", r);
}

fn transaction<'a>(
    db: &'a AppDatabase,
    session: &'a mut DatabaseSession,
) -> BoxFuture<'a, anyhow::Result<()>> {
    let val = 5;
    async move { db.insert_with_session(val, session).await }.boxed()
}

struct AppDatabase(i32);
struct DatabaseSession(i32);

impl AppDatabase {
    async fn execute_transaction<F>(&self, f: F) -> anyhow::Result<()>
    where
        F: for<'a> Fn(
            &'a AppDatabase,
            &'a mut DatabaseSession,
        ) -> BoxFuture<'a, anyhow::Result<()>>,
    {
        let mut session = DatabaseSession(0);
        let result = f(&self, &mut session).await;
        if result.is_err() {
            println!("rollback transaction here");
            result
        } else {
            println!("commit transaction here");
            Ok(())
        }
    }

    async fn insert_with_session(
        &self,
        val: i32,
        session: &mut DatabaseSession,
    ) -> anyhow::Result<()> {
        println!("some dummy insert operation here");
        session.0 = val;
        Ok(())
    }
}

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.