【问题标题】:Cannot move out because of borrowing因借款无法搬出
【发布时间】:2023-03-11 17:59:01
【问题描述】:

我有以下 MWE:

use rusqlite::{Connection, Transaction };

fn main() {
    let mut cn: Connection = Connection::open_in_memory().unwrap();
    let tx: Transaction = cn.transaction().unwrap();

    let mut stmt = tx.prepare("
        INSERT INTO mytable (data) VALUES(1)
    ").unwrap();
    stmt.execute([]).unwrap();

    tx.commit().unwrap();
}

在我的 IDE(vscode)中,它显示:

tx.prepare 显示错误

在此处借用tx

tx.commit(); 显示错误

不能搬出tx,因为它是借来的

签名:

 pub fn prepare(&self, sql: &str) -> Result<Statement<'_>> {
        self.db.borrow_mut().prepare(self, sql)
 }
 ...
 #[inline]
 pub fn commit(mut self) -> Result<()> {
     self.commit_()
 }

我不明白为什么会显示这些错误。

  • tx.prepare 借用后,Transaction 创建并分配给tx 的所有权是什么?
  • tx 仍然是Transaction 的所有者吗?
  • tx.commit()被调用时,Transaction应该归tx所有,并且在调用.commit()时可以安全移动,不是吗?

此外,确定tx.prepare 的范围可以修复这些错误

use rusqlite::{Connection, Transaction };

fn main() {
    let mut cn: Connection = Connection::open_in_memory().unwrap();
    let tx: Transaction = cn.transaction().unwrap();
    {
        let mut stmt = tx.prepare("
            INSERT INTO mytable (data) VALUES(1)
        ").unwrap();
        stmt.execute([]).unwrap();
    }
    tx.commit().unwrap();
}

谢谢!

Rust Playground

【问题讨论】:

    标签: rust ownership


    【解决方案1】:

    错误信息中给出了这个原因:

    14 | }
       | - borrow might be used here, when `stmt` is dropped and runs the `Drop` code for type `Statement`
    

    Transaction 上调用prepare()(通过自动取消引用传递给Connection)会给你一个Statement&lt;'conn&gt;。换句话说,Statement 借用了基础Connection,其生命周期为'connConnection 的借用取决于 Transaction,因为它是来自 Transaction 的自动取消引用,它给了我们在 Connection 上的借用。

    每当删除 Statement 时,它都会使用其借用 Connectionclean up behind it。这就是问题出现的地方:您的stmttx.commit().unwrap() 之后自动删除到作用域的末尾。同时.commit() 拥有self 的所有权,所以Transaction 被移走。但StatementDrop 实现要求Transaction 的借用是活动的,因此它可以在Connection 的借用上执行;所以出现错误。

    您可以通过简单地引入内部范围来解决此问题,或者在使用后手动 drop(stmt)

    【讨论】:

    • 感谢您的解释。我不得不读了好几遍,但这是有道理的:)
    【解决方案2】:

    tx.prepare 借用后,Transaction 创建并分配给tx 的所有权是什么?

    不确定我是否理解您的要求。借用不会改变所有权,它会所有者借用。所以tx拥有交易对象,stmt借用它,所以所有权不能改变。

    tx 仍然是交易的所有者吗?

    是的。

    tx.commit()被调用时,交易应该归tx所有,并且在调用.commit()时可以安全移动,不是吗?

    在大多数情况下是的,这里的问题是 Statement 不是“微不足道”的类型,它实现了 Drop 这意味着它(默认情况下)将一直存活到 drop scope 的末尾,除非它是明确的丢弃(例如通过调用drop(stmt))。然后,这将与 Transaction::commit 一起编译,因为您无法退出“活动借用”。

    您只需安装与 Rusqlite 匹配的类型方案即可观察此行为:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=32532b41467706d0d80756525a0f43fe

    代码会编译,但如果您取消注释第 24 行 (impl Drop for …),它会停止编译,并出现与此处相同的错误。

    您的解决方案是:

    • 如果 MRE 与实际代码匹配,则无需费心准备语句,这仅在您有多个查询时才有用
    • 完成后明确drop() 声明
    • 或者让它存在于自己的块中,所以它的放置范围比函数短

    【讨论】:

    • 感谢您的解释。我还有一个问题:如果 l24 被注释掉,它可以工作。没有默认的 drop() executed 来清理 Statement 吗?如果有,我们不应该有同样的问题(关于主动借款期间的移动)吗?
    • @ccheneson 不,没有默认值 drop() 因为“普通”类型没有要执行的清理(它们只是堆栈上的内存,因此随着堆栈帧消失),所以没有语义影响可能与该清理有关,因此它们在最后一次使用结束时就“死”了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-23
    • 2018-04-04
    • 1970-01-01
    • 2018-04-18
    • 1970-01-01
    相关资源
    最近更新 更多