为什么对已删除对象的可变引用仍被视为可变引用?

时间:2017-01-19 01:43:38

标签: rust lifetime

这是一个简化的例子:

struct Connection {}

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

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

impl<'conn> Transaction<'conn> {
    fn commit(mut self) {}
}

fn main() {
    let mut db_conn = Connection {};

    let mut trans = db_conn.transaction(); //1
    let mut records_without_sync = 0_usize;
    const MAX_RECORDS_WITHOUT_SYNC: usize = 100;
    loop {
        //do something
        records_without_sync += 1;
        if records_without_sync >= MAX_RECORDS_WITHOUT_SYNC {
            trans.commit();
            records_without_sync = 0;
            trans = db_conn.transaction(); //2
        }
    }
}

编译器在 1 2 报告两个可变借词,但事实并非如此。由于trans.commit()按值self计算,trans会被删除,因此点 2 应该没有可变引用。

  1. 为什么编译器看不到 2 没有可变引用?
  2. 如何修复代码,保留相同的逻辑?

2 个答案:

答案 0 :(得分:2)

有一个可变的参考。

如果您将transaction更改为:

fn transaction(&mut self) -> Transaction {
    let _: () = self;
    Transaction{conn: self}
}

您将看到编译器错误:

 = note: expected type `()`
 = note:    found type `&mut Connection`

所以self属于&mut Connection类型......一个可变引用。然后,您将其传递到从此函数返回的Transaction实例中。

这意味着你的可变借用在trans的生命周期内存在(由我添加的花括号来显示借用的范围):

let mut trans = db_conn.transaction();
{ // <-------------------- Borrow starts here
    let mut records_without_sync = 0_usize;
    const MAX_RECORDS_WITHOUT_SYNC: usize = 100;
    loop {
        //do something
        records_without_sync += 1;
        if records_without_sync >= MAX_RECORDS_WITHOUT_SYNC {
            trans.commit();
            records_without_sync = 0;
            trans = db_conn.transaction();// <--- ####### D'oh! Still mutably borrowed
        }
    }
} // <-------------------- Borrow ends here

如果您正在寻找此类parent-><-child设置,我认为您必须联系到Rc<RefCell>

具体来说,引用的Rc计算您传递连接的次数和RefCell来跟踪在运行时而不是编译时间的借用。是的,这确实意味着如果你设法在运行时尝试并可变地借两次,那么你会感到恐慌。如果不了解您的架构,很难说这是否合适。

Here is my solution anyway

use std::cell::RefCell;
use std::rc::Rc;

struct Connection {}

impl Connection {
    fn do_something_mutable(&mut self) {
        println!("Did something mutable");
    }
}

type Conn = Rc<RefCell<Connection>>;

struct Transaction {
    conn: Conn,
}

impl Transaction {
    fn new(connection: Conn) -> Transaction {
        Transaction { conn: connection }
    }

    fn commit(mut self) {
        self.conn.borrow_mut().do_something_mutable();
    }
}

fn main() {
    let db_conn = Rc::new(RefCell::new(Connection {}));

    let mut trans = Transaction::new(db_conn.clone());
    let mut records_without_sync = 0_usize;
    const MAX_RECORDS_WITHOUT_SYNC: usize = 100;
    loop {
        //do something
        records_without_sync += 1;
        if records_without_sync >= MAX_RECORDS_WITHOUT_SYNC {
            trans.commit();
            records_without_sync = 0;
            trans = Transaction::new(db_conn.clone());
            break; // Used to stop the loop crashing the playground
        }
    }
}

答案 1 :(得分:2)

启用non-lexical lifetimes时原始代码有效:

#![feature(nll)]

struct Connection {}

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

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

impl<'conn> Transaction<'conn> {
    fn commit(self) {}
}

fn main() {
    let mut db_conn = Connection {};

    let mut trans = db_conn.transaction();
    let mut records_without_sync = 0_usize;
    const MAX_RECORDS_WITHOUT_SYNC: usize = 100;
    loop {
        //do something
        records_without_sync += 1;
        if records_without_sync >= MAX_RECORDS_WITHOUT_SYNC {
            trans.commit();
            records_without_sync = 0;
            trans = db_conn.transaction();
        }
    }
}

非词汇生命周期提高了借阅检查器的精确度。编译器变得更加智能,现在能够证明更多程序是内存安全的。