Lifetime may not live long enough for an async closure

I am trying to define a database connection trait for transaction.

#[async_trait]
pub trait Connection {
    async fn begin_transaction(&mut self) -> Result<(), Error>;
    async fn commit_transaction(&mut self) -> Result<(), Error>;
    async fn rollback_transaction(&mut self) -> Result<(), Error>;

    async fn transaction<T, F, R>(&mut self, f: F) -> Result<T, Error>
    where
        T: Send,
        F: FnOnce(&mut Self) -> R + Send,
        R: Future<Output = Result<T, Error>> + Send,
    {
        self.begin_transaction().await?;
        let result = f(self).await;

        if result.is_ok() {
            self.commit_transaction().await?;
        } else {
            self.rollback_transaction().await?;
        }
        result
    }
}

The trait itself actually compiles, but the compilation fails when I try to use it like the following code.

let mut foo = Foo(); // = impl Connection
foo.transaction(|foo| async move {
    Ok(println!("{:?}", foo))
}).await
error: lifetime may not live long enough
  --> src/main.rs:48:27
   |
48 |       foo.transaction(|foo| async move {
   |  ______________________----_^
   | |                      |  |
   | |                      |  return type of closure `impl Future` contains a lifetime `'2`
   | |                      has type `&'1 mut Foo`
49 | |         Ok(println!("{:?}", foo))
50 | |     }).await
   | |_____^ returning this value requires that `'1` must outlive `'2`

Here's the constraints.

  • All of the Connection::xx_transaction methods need &mut self as an argument.
  • I want to solve this within the Connection trait and its usage.
    • Don't want to define extra traits/structs if possible.

You can see the full example code here.

How can I make this work?

I suspect the problem is that |foo| given in the callback is deliberately limited to be valid only immediately within that closure and it's not allowed to be "smuggled" outside of it via returned future. There is no lifetime connecting FnOnce(&mut Self) borrow with the lifetime of the returned R future, so the R future is not allowed to be related to the Self reference in any way.

async move doesn't do anything to references. It can only move owned values, but you can't own data behind a reference.

Another problem is that lifetimes of &mut references are invariant (i.e. completely inflexible and can't be extended or shortened, except reborrow sometimes), so if you put &'a mut the borrow checker is likely to be overly pedantic about it.

Maybe you can you use F: for<'a> FnOnce(&'a Self) -> Pin<Box<dyn Future<yadayada> + 'a>>?

You're going to need for<'a> syntax to create a lifetime just for the function call, and not put anything on the struct/trait, because that would tie the single call to lifetime of the whole connection. But AFAIK for<'a> won't let you to put the same lifetime on -> R (it's a limitation of the syntax), that's why you're going to need to box the future.

2 Likes

Yeah, this is currently the only solution that works reliably:

  use ::async_trait::async_trait; // 0.1.50
  use ::core::future::Future;
+ use ::futures::future::{BoxFuture, FutureExt};
  use ::tokio; // 1.8.1
  
  #[async_trait]
  pub trait Connection {
      async fn begin_transaction(&mut self) -> Result<(), ()>;
      async fn commit_transaction(&mut self) -> Result<(), ()>;
      async fn rollback_transaction(&mut self) -> Result<(), ()>;
  
-     async fn transaction<T, F, R>(&mut self, f: F) -> Result<T, ()>
+     async fn transaction<T, F   >(&mut self, f: F) -> Result<T, ()>
      where
          T: Send,
-         F: Send + FnOnce(&mut Self) -> R,
+         F: Send + FnOnce(&mut Self) -> BoxFuture<'_, Result<T, ()>>,
-         R: Future<Output = Result<T, ()>> + Send,
      {
          self.begin_transaction().await?;
          let result = f(self).await;
  
          if result.is_ok() {
              self.commit_transaction().await?;
          } else {
              self.rollback_transaction().await?;
          }
          result
      }
  }
  
  #[derive(Debug)]
  struct Foo();
  
  #[async_trait]
  impl Connection for Foo {
      async fn begin_transaction(&mut self) -> Result<(), ()> {
          Ok(())
      }
      async fn commit_transaction(&mut self) -> Result<(), ()> {
          Ok(())
      }
      async fn rollback_transaction(&mut self) -> Result<(), ()>  {
          Ok(())
      }
  }
  
  #[tokio::main]
  async fn main() -> Result<(), ()> {
      let mut foo = Foo();
      foo.transaction(|foo| async move {
          Ok(println!("{:?}", foo))
-     }        ).await
+     }.boxed()).await
  }

You guys saved my day!

Thanks to @kornel for letting me know what's going on and @Yandros for providing the playground link to the solution.