我如何使用具有 Deref 特性的多态性来拥有一个可以由 Transaction 或 Connection 表示的对象?

How can I use polymorphism with the Deref trait to have a a single object that can be represented by either a Transaction or Connection?

我正在使用 rustqlite library for a SQLite database (playground)

use std::ops::Deref;

pub struct Connection {
}

impl Connection {
  pub fn transaction(&mut self) -> Transaction {
    Transaction::new(self)
  }
}

pub struct Transaction<'conn> {
    conn: &'conn Connection
}

impl Transaction<'_> {
    pub fn new(conn: &mut Connection) -> Transaction {
        Transaction{ conn }
    }
    
    pub fn commit(mut self) -> Result<(), ()> {
        Ok(())
    }
}

impl Deref for Transaction<'_> {
    type Target = Connection;

    #[inline]
    fn deref(&self) -> &Connection {
        self.conn
    }
}

通过此实现,Transaction 对象将取得 Connection 对象的所有权。同时,它还实现了 Deref 特性,因此我们可以像调用 Connection 结构一样调用 Transaction 结构中的所有方法。

实现细节是here

在我的应用程序代码中,我想要一个可以用 TransactionConnection 表示的对象。这是必要的,因为逻辑有一个标志来决定是否使用事务。有一个转换将 Transaction 对象视为 Connection 对象:

let conn = create_connection(); // Connection
let tx = conn.transaction(); // Transaction
let conn: &Transaction = &tx; // cast back to Connection type from the Transaction type

但是,我不知道如何从具有条件的应用程序 POV 安排此代码。这是我的伪代码:

pub fn execute(is_tx: bool) {
    // conn will have Connection type
    let conn = match is_tx {
        true => &create_connection(),
        false => {
            let x = create_connection().transaction();
            let t: &Connection = &x;
            t
        }
    };

    // do other things with conn object
}

pub fn create_connection() -> Connection {
    Connection{}
}

但是会报错

error[E0716]: temporary value dropped while borrowed
  --> src/lib.rs:36:21
   |
36 |             let x = create_connection().transaction();
   |                     ^^^^^^^^^^^^^^^^^^^              - temporary value is freed at the end of this statement
   |                     |
   |                     creates a temporary which is freed while still in use
37 |             let t: &Connection = &x;
   |                                  -- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error[E0597]: `x` does not live long enough
  --> src/lib.rs:37:34
   |
37 |             let t: &Connection = &x;
   |                                  ^^ borrowed value does not live long enough
38 |             t
   |             - borrow later used here
39 |         }
   |         - `x` dropped here while still borrowed

我理解这个错误,但我尝试了几个变通方法但没有成功,主要是因为 Transaction 结构取得了 Connection 结构的所有权。我该如何解决这个问题?

免责声明:我还没有尝试过这段代码。但这只是为了让您了解进入的方向。

首先,Rust 中的局部变量(在堆栈上)必须是固定大小的。这是您面临的问题之一。事务和连接的大小不同。所以你不能在栈上实现“多态”,没有一些技巧。

实现此目的的两种方法是枚举类型和装箱(将结构放在堆上,并添加一个 VTable)。

我不会讲拳击,因为它相对简单。

您遇到的第二个问题是 Transaction 的生命周期与 Connection 相关,因此 Transaction 的任何移动都需要您 move Connection 还有。

enum MyConnection<'a> {
   TransactionConnection {
       transaction: Transaction<'a>
    },
   NakedConnection{
    connection: Connection
   }
}

impl MyConnection<'a> {
    fn commit(mut &self) -> Result<()> {
        match self {
           MyConnection::NakedConnection =>
              Ok(()),
           MyConnection::TransactionConnection { transaction } =>
              transaction.commit()
        }
    }
}

impl<'a> Deref for MyConnection<'a>
{
    type Target = Connection;
    #[inline]
    fn deref(&self) -> &Connection {
        match self {
            MyConnection::TransactionConnection { transaction } =>
                transaction.conn,
            MyConnection::NakedConnection { connection } =>
                connection,
        }
    }
}

这些枚举和 Deref 将允许您持有一个可以访问连接的结构。

这就是您使用上述代码的方式。

pub fn execute(is_tx: bool) {
    // conn will have Connection type
    let mut conn = create_connection();
    let conn = match is_tx {
        false => {
            MyConnection::NakedConnection { connection: conn }
        },
        true => {
            let trans = conn.transaction();
            MyConnection::TransactionConnection {                
                transaction: trans,
            }
        }
    };
    conn.do_stuff();
    conn.commit();
}

请注意 create_connection 已移出匹配项。这样连接的范围将始终大于 MyConnection 的范围 'a。这“解决”了第二个问题。