I'm having some trouble understanding lifetimes as they relate to futures. I've read the other posts on here I could find related to the "not general enough" error, and while I vaguely understand them, I don't understand well enough to apply them to my situation. I'm also relatively new to async programming in Rust.
I've got a struct that's holding onto a Box<tokio::sync::Mutex<sqlx::Transaction>>
, and I'd like to use that transaction in the context of a generic function that takes any connection that implements sqlx::Acquire
. I'd like to call that generic function from a method on an async_trait
decorated trait. However, I get an error: implementation of sqlx::Acquire is not general enough
.
Through getting this down to a minimal reproducible example and removing use of async_trait
, I've figured out that the error comes from the Send
requirement that async_trait
adds to the bounds of the return type.
I am able to implement my trait using a struct that just holds a sqlx::PgPool
, where I'm passing a &PgPool
into the method, so it seems to be somehow related to the lifetime of the Transaction
itself, but even if I set this lifetime to static
, I get the same error.
However, I am struggling to understand why that's the case, so if anyone could help me understand the following, I'd really appreciate it.
- Why does the
Send
bound lead totrait is not general enough
? - Is there any way to do what I'm trying to do?
- I could remove the
Send
bound, but is that a bad idea? Generally, none of the implementations of the trait are spawning new threads or futures, so it seems like removing the bound might be okay. What would I be losing by removing the bound?
My minimal-ish reproduction is below. Unfortunately I haven't yet been able to figure out how to reproduce with a custom trait, so the reproduction has sqlx as a dependency.
// so we've got some function that uses something that implements Acquire (impl
// syntax vs. normal generic syntax doesn't make any difference)
async fn uses_txn<'a>(cxn: impl sqlx::Acquire<'a, Database = Postgres>) {
let mut cxn = cxn.acquire().await.unwrap();
let _exec = cxn.deref_mut();
}
// A non-async trait version, should be basically the same as what async_trait
// expands to
trait UseTxnSend {
fn use_txn<'a>(&'a self) -> Pin<Box<dyn core::future::Future<Output = ()> + Send + 'a>>;
}
// A non-async trait version, without the Send requirement
trait UseTxnNoSend {
fn use_txn<'a>(&'a self) -> Pin<Box<dyn core::future::Future<Output = ()> + 'a>>;
}
// We've got a thing that holds an owned transaction in a mutex (put it in a box
// so that it's Send).
struct TxnHolder<'a> {
txn: Box<tokio::sync::Mutex<sqlx::Transaction<'a, Postgres>>>,
}
// regualr impl
impl<'a> TxnHolder<'a> {
// An async method that uses the txn and calls the function.
// No compiler error here!
async fn use_txn(&self) -> () {
let txn = &mut *self.txn.lock().await;
uses_txn(txn).await
}
}
impl<'a> UseTxnNoSend for TxnHolder<'a> {
// Implementing without the Send requirement has no errors.
fn use_txn<'b>(&'b self) -> Pin<Box<dyn std::future::Future<Output = ()> + 'b>> {
Box::pin(async move {
let txn = &mut *self.txn.lock().await;
uses_txn(txn).await
})
}
}
impl<'a> UseTxnSend for TxnHolder<'a> {
// The send requirement leads to the `Acquire is not general enough` error
fn use_txn<'b>(&'b self) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'b>> {
Box::pin(async move {
let txn = &mut *self.txn.lock().await;
uses_txn(txn).await
})
}
}
for reference, the signatures of Acquire
and its associated type trait Database
are reproduced below (from here):
pub trait Acquire<'c> {
type Database: Database;
type Connection: Deref<Target = <Self::Database as Database>::Connection> + DerefMut + Send;
fn acquire(self) -> BoxFuture<'c, Result<Self::Connection, Error>>;
fn begin(self) -> BoxFuture<'c, Result<Transaction<'c, Self::Database>, Error>>;
}
impl<'a, DB: Database> Acquire<'a> for &'_ Pool<DB> {
type Database = DB;
type Connection = PoolConnection<DB>;
fn acquire(self) -> BoxFuture<'static, Result<Self::Connection, Error>> {
Box::pin(self.acquire())
}
fn begin(self) -> BoxFuture<'static, Result<Transaction<'a, DB>, Error>> {
let conn = self.acquire();
Box::pin(async move {
Transaction::begin(MaybePoolConnection::PoolConnection(conn.await?)).await
})
}
}
I've been banging on this for three days with very little progress, so thanks in advance for any help you can give!