问题
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.
- Why can the compiler not see that at 2 there are no mutable references?
- 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