Why does a mutable reference to a dropped object still count as a mutable reference?

ε祈祈猫儿з 提交于 2019-12-23 18:31:53

问题


Here is a simplified example:

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

The compiler reports two mutable borrows at 1 and 2, but that is not the case. Since trans.commit() takes self by value, trans is dropped, so by point 2 there should be no mutable references.

  1. Why can the compiler not see that at 2 there are no mutable references?
  2. How can I fix the code, leaving the same logic?

回答1:


There is a mutable reference.

If you change transaction to this:

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

You'll see that the compiler errors with:

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

So self is of type &mut Connection ... a mutable reference. You're then passing this into the Transaction instance that is being returned from this function.

That means your mutable borrow exists for the lifetime of trans (curly braces added by me to show the scope of the borrow):

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

If you're looking for this sort of parent-><-child setup, I think you'll have to reach for Rc<RefCell>.

Specifically, an Rc to reference count how many times you pass the connection around and RefCell to track borrowing at runtime instead of compile time. Yes, that does mean you'll panic if you manage to try and mutably borrow it twice at runtime. Without knowing more about your architecture its hard to say whether this is suitable or not.

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



回答2:


Your original code works when non-lexical lifetimes are enabled:

#![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();
        }
    }
}

Non-lexical lifetimes improve the precision of the borrow checker. The compiler has become smarter and it is now able to prove that more programs are memory safe.



来源:https://stackoverflow.com/questions/41732404/why-does-a-mutable-reference-to-a-dropped-object-still-count-as-a-mutable-refere

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!