at54321
at54321

Reputation: 11758

Ownership/borrowing issues when trying to get a DB connection and tx from a separate function

I have a common function that returns a MySQL connection:

pub fn get_db_conn() -> Result<PooledConn> {...}

And all around the application I have code like this:

let mut conn = get_db_conn()?;
let mut tx = conn.start_transaction(TxOpts::default())?;

That kinda breaks the DRY principle and I'd like to simplify that code by only having to call a single function. So I image something like this:

pub fn get_db_conn_and_tx() -> Result<(PooledConn, Transaction)> {
    let mut conn = get_db_conn()?;
    let mut tx = conn.start_transaction(TxOpts::default())?;
    Ok((conn, tx))
}

or this:

pub fn get_db_tx() -> Result<(Transaction)> {
    let mut conn = get_db_conn()?;
    let mut tx = conn.start_transaction(TxOpts::default())?;
    Ok((tx))
}

However, neither of those compiles.

Let's take the first one as an example. This is the error the compiler emits:

error[E0106]: missing lifetime specifier
   |
33 | pub fn get_db_conn_with_tx() -> Result<(PooledConn, Transaction)> {
   |                                                     ^^^^^^^^^^^ expected named lifetime parameter
   |
   = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
   |
33 | pub fn get_db_conn_with_tx() -> Result<(PooledConn, Transaction<'static>)> {
   |                                                     ~~~~~~~~~~~~~~~~~~~~

I tried changing the result to Result<(PooledConn, Transaction<'static>)>. This time I got:

error[E0515]: cannot return value referencing local variable `conn`
   |
35 |     let mut tx = conn.start_transaction(TxOpts::default())?;
   |                  ---- `conn` is borrowed here
36 |     Ok((conn, tx))
   |     ^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

Why do I get that error message? I am returning both conn and tx, so shouldn't ownership of both values be transferred to the calling function?

Also, I wonder what would be the best way to implement the second function that returns just a Transaction.

Upvotes: 1

Views: 64

Answers (1)

rodrigo
rodrigo

Reputation: 98436

Your code is equivalent to this one:

fn tuple() -> (i32, &i32) {
    let x = 42;
    (x, &x)
}

That produces the same error message:

1 | fn tuple() -> (i32, &i32) {
  |                     ^ expected named lifetime parameter

This issue is called "self referential structs" (self referential tuple in your case, but the same concept), and is a persistent pain for many Rust users, because it does not have a full satisfactory solution.

There a bunch of third party crates that tackle this problem. My personal favorite is currently ouroboros.

But if you just want to avoid typing two lines you can use a macro, not pretty, but it is short (you can add your database code instead of numbers):

macro_rules! self_ref {
    ($r:ident) => {
        let $r = 42;
        //Shadow the first variable, it is borrowed anyway
        let $r = &$r;
    }
}

fn main() {
    self_ref!(r);
    dbg!(r);
}

But for your particular case of a DB connection and a transaction, I don't think it is worth it, I would just use two variables and two function calls.

Upvotes: 1

Related Questions