I'm facing a problem with generic associated types and asynchronous rust.
The problem is: I want to be able to create a function called transaction
in a trait Connection
. This method basically creates a transaction and sends it in a callback function. If the callback returns Ok, we commit the transaction. Otherwise, we roll back. Simple, right? Here’s the code:
use anyhow::Result;
use futures::future::BoxFuture;
#[async_trait::async_trait]
trait Transaction: Send + Sync {
async fn commit(self) -> Result<()>;
async fn rollback(self) -> Result<()>;
async fn query(
&mut self,
sql: &str,
) -> Result<()>;
}
#[async_trait::async_trait]
trait Connection: Send + Sized {
type Tx<'t>: Transaction
where
Self: 't;
async fn begin(&self) -> Result<Self::Tx<'_>>;
fn transaction<'a, F, R, E>(
&'a mut self,
callback: F,
) -> BoxFuture<Result<R, E>>
where
for<'c> F:
FnOnce(&'c mut Self::Tx<'_>) -> BoxFuture<'c, Result<R, E>> + 'a + Send + Sync,
R: Send,
E: From<anyhow::Error> + Send,
{
Box::pin(async move {
let mut tx = self.begin().await?;
let ret = callback(&mut tx).await;
match ret {
Ok(ret) => {
tx.commit().await?;
Ok(ret)
}
Err(err) => {
tx.rollback().await?;
Err(err)
}
}
})
}
}
#[tokio::main]
async fn main() -> Result<()> { Ok(()) }
The problem is that I want to use a generic associated type in this trait called Tx
that defines the transaction type.
The associated type has a generic lifetime ‘t, because the transaction is tied to the connection. That’s where the problem resides and it throws the error Self does not live long enough
Does anyone have any suggestions on top of that?
I tried to remove the generic associated type like sqlx does, and it works. However I need to use a GAT.
Edited
I found an answer to make it compile: change the return type of the function from BoxFuture
to LocalBoxFuture
.
That makes the code above compile fine, but that's not a reasonable solution for my current scenario because I need to guarantee Send
ness. So, I'm looking for alternatives.