Lifetimes problem with postgres::Transaction

Hello, Rustaceans :wave:

Given this code (also as a Rust playground link):

use postgres::{Client, Transaction};

pub trait Database<'a> {
    type Connection: DBConn<'a>;
    fn connect(&self) -> Result<Self::Connection, postgres::Error>;
}

pub trait DBConn<'a> {
    type Transaction: DBTxn<'a>;
    fn start_transaction(&'a mut self) -> Result<Self::Transaction, postgres::Error>;
}

pub trait DBTxn<'a> {
    fn application_logic(&mut self) -> Result<(), postgres::Error>;
}

pub struct Pg;

impl<'a> Database<'a> for Pg {
    type Connection = PgConn;

    fn connect(&self) -> Result<Self::Connection, postgres::Error> {
        todo!()
    }
}

pub struct PgConn(Client);

impl<'a> DBConn<'a> for PgConn {
    type Transaction = PgTxn<'a>;

    fn start_transaction(&'a mut self) -> Result<PgTxn<'a>, postgres::Error> {
        let db_txn = self.0.transaction()?;
        Ok(PgTxn(db_txn))
    }
}

pub struct PgTxn<'a>(Transaction<'a>);

impl<'a> DBTxn<'a> for PgTxn<'_> {
    fn application_logic(&mut self) -> Result<(), postgres::Error> {
        todo!()
    }
}

fn foo<'a, D: Database<'a>>(db: &D) -> Result<(), postgres::Error>
    where
        D::Connection: 'a
{
    let mut conn = db.connect()?;
    let mut db_txn = conn.start_transaction()?;
    db_txn.application_logic().unwrap();
    Ok(())
}

fn main() {
    let db = Pg {};
    foo(&db);
}

I'm getting this compilation error:

Compiling playground v0.0.1 (/playground)
error[E0597]: `conn` does not live long enough
  --> src/main.rs:51:22
   |
46 | fn foo<'a, D: Database<'a>>(db: &D) -> Result<(), postgres::Error>
   |        -- lifetime `'a` defined here
...
51 |     let mut db_txn = conn.start_transaction()?;
   |                      ^^^^--------------------
   |                      |
   |                      borrowed value does not live long enough
   |                      argument requires that `conn` is borrowed for `'a`
...
54 | }
   | - `conn` dropped here while still borrowed

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

I need for db_txn to live shorter than conn but have troubles encoding that with lifetimes and associated types. Why is the compiler claiming that conn doesn't live long enough here?

What happens if I decide to call the function foo<'static, Pg>? Then we know that it implements the trait Database<'static>, so the type of D::Connection must implement the trait DBConn<'static>.

But now the start_transaction method on the connection then takes an &'static mut self as argument. You don't have a 'static reference to conn, since it goes out of scope at the end of foo, hence you can't call it.

To fix it, do this instead:

fn foo<D: for<'a> Database<'a>>(db: &D) -> Result<(), postgres::Error> {
    let mut conn = db.connect()?;
    let mut db_txn = conn.start_transaction()?;
    db_txn.application_logic().unwrap();
    Ok(())
}

Now you are saying that D must implement all of the following traits:

  1. Database<'a>
  2. Database<'b>
  3. Database<'c>
  4. Database<'d>
  5. Database<'e>

and so on for every possible lifetime. Since it includes every lifetime, the needed lifetime inside foo is on the list.

Another possibility would be to try and change the Database trait like this:

pub trait Database {
    type Connection: for<'a> DBConn<'a>;
    fn connect(&self) -> Result<Self::Connection, postgres::Error>;
}

but I'm not sure if it will work.

1 Like

I'm afraid the issue persists: Playground.

Compiling playground v0.0.1 (/playground)
error[E0597]: `conn` does not live long enough
  --> src/main.rs:49:22
   |
49 |     let mut db_txn = conn.start_transaction()?;
   |                      ^^^^ borrowed value does not live long enough
...
52 | }
   | -
   | |
   | `conn` dropped here while still borrowed
   | borrow might be used here, when `conn` is dropped and runs the destructor for type `<D as Database<'_>>::Connection`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

This works!

Thank you a ton, @alice !