Ownership/borrowing 尝试从单独的函数获取数据库连接和 tx 时出现问题

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

我有一个共同的功能,即returns一个MySQL连接:

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

在整个应用程序中,我都有这样的代码:

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

这有点违反了 DRY 原则,我想通过只调用一个 single 函数来简化代码。所以我想像这样:

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))
}

或者这个:

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

然而,这些都无法编译。

我们以第一个为例。这是编译器发出的错误:

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>)> {
   |                                                     ~~~~~~~~~~~~~~~~~~~~

我尝试将结果更改为 Result<(PooledConn, Transaction<'static>)>。这次我得到了:

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

为什么我会收到该错误消息?我要返回 both conntx,所以不应该将这两个值的所有权转移给调用函数吗?

此外,我想知道实现第二个功能的最佳方法是什么 returns 只是一个 Transaction

你的代码等同于这个:

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

产生相同的错误消息:

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

这个问题叫做“自引用结构”(在你的例子中是自引用元组,但概念相同),对许多 Rust 用户来说是一个持续的痛苦,因为它没有完全令人满意的解决方案。

有很多第三方 crate 可以解决这个问题。目前我个人最喜欢的是 ouroboros.

但如果你只是想避免输入两行,你可以使用宏,虽然不漂亮,但它很短(你可以添加你的数据库代码而不是数字):

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);
}

但是对于您的数据库连接和事务的特定情况,我认为不值得,我只会使用两个变量和两个函数调用。