2

I need to wrap Sqlx connection and Transaction types into my corresponding opaque types that will implement domain-specific data access methods. Trying to achieve that I'm struggling to support spawning of transactions. Here is my simplified code:

use sqlx::{
    Connection, Database, Sqlite, SqliteConnection as SqlxSqliteConnection,
    Transaction as SqlxTransaction,
};

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("unknown error")]
    Unknown,
}

pub type Result<T> = std::result::Result<T, Error>;

pub type Future<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;

pub struct Transaction<'c, DB: Database>(SqlxTransaction<'c, DB>);

pub trait DataConn<'c, DB: Database>
where
    &'c mut Transaction<'c, DB>: DataTx<'c, DB>,
{
    fn begin(self) -> Future<'c, Result<Transaction<'c, DB>>>;
}

pub trait DataTx<'c, DB: Database>
where
    &'c mut Transaction<'c, DB>: DataTx<'c, DB>,
{
    fn begin(self) -> Future<'c, Result<Transaction<'c, DB>>>;
}

pub struct SqliteConnection(SqlxSqliteConnection);

impl<'c> DataConn<'c, Sqlite> for &'c mut SqliteConnection {
    fn begin(self) -> Future<'c, Result<Transaction<'c, Sqlite>>> {
        Box::pin(async move {
            Ok(Transaction(Connection::begin(&mut self.0).await?))
        })
    }
}

impl<'c> DataTx<'c, Sqlite> for &'c mut Transaction<'c, Sqlite> {
    fn begin(self) -> Future<'c, Result<Transaction<'c, Sqlite>>> {
        Box::pin(async move { Ok(Transaction(self.0.begin().await?)) })
    }
}

#[cfg(test)]
mod test {
    use super::*;

    async fn test_transactions<'c, DB, Conn>(conn: Conn)
    where
        DB: Database,
        Conn: DataConn<'c, DB>,
        &'c mut Transaction<'c, DB>: DataTx<'c, DB>,
    {
        let mut tx = conn.begin().await.unwrap();
        let tx2 = tx.begin().await.unwrap(); // fails here
    }
}

As a result I get the following error details:

error[E0597]: `tx` does not live long enough
  --> src/data2/mod.rs:54:19
   |
47 |     async fn test_transactions<'c, DB, Conn>(conn: Conn)
   |                                -- lifetime `'c` defined here
...
54 |         let tx2 = tx.begin().await.unwrap();
   |                   ^^^^^^^^^^
   |                   |
   |                   borrowed value does not live long enough
   |                   argument requires that `tx` is borrowed for `'c`
...
57 |     }
   |     - `tx` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.

Can you explain why tx2 does outlive tx in the test function below and how I can fix that?

ababo
  • 1,490
  • 1
  • 10
  • 24
  • 3
    Please always post the **full** error message. – Chayim Friedman May 29 '22 at 22:08
  • The previous was a message from rust-analyser in Visual Studio Code. Now I have replaced it with the original message from rustc. – ababo May 30 '22 at 07:16
  • 1
    Out of curiosity I rewrote your code from both questions [into my own version that compiles](https://gist.github.com/kotatsuyaki/81d7d6758797af71b3abbc636f3309bb). Not really an answer though, since I'm not really caring about the functionality. – kotatsuyaki May 30 '22 at 13:09
  • @kotatsuyaki, your code doesn't work with references. In order to support different database types I need to implement the traits for mutable and immutable references. This leads to the problem of this post. See https://gist.github.com/ababo/ae0e081621e0eb3df4c88f967d0808e0 – ababo May 30 '22 at 16:10
  • @kotatsuyaki, as it turns out there's no strict constraint to support immutable references (indeed sqlx transactions use &mut for all the supported database types and I don't plan to use interior mutability), so your solution works pretty well (if to generalise it a bit). Thank you! Although I got rid of struct in the transaction trait by using GAT (yet only in Rust nightly) which made my code much cleaner. – ababo May 31 '22 at 06:16
  • @ababo Glad that you got the problem solved. – kotatsuyaki May 31 '22 at 06:28

0 Answers0