I am designing some struct with a prepare-commit pattern in rust, which mostly looks like this after simplified:
struct Data {
a: usize,
}
struct Adder<'a> {
data: &'a mut Data,
added: usize,
}
struct Holder<'a> {
data: &'a Data,
}
impl<'a> Adder<'a> {
fn add_once(&mut self) {
// Needs added to be mut, but not data
self.added = self.added + self.data.a;
}
fn commit(&mut self) {
// Needs data to be mut, but not added
self.data.a = self.added;
}
}
fn main() {
let mut d = Data { a: 1 };
let h = Holder { data: &d };
let mut ad = Adder {
data: &mut d,
added: 0,
};
ad.add_once();
ad.add_once();
println!("{}", h.data.a); // Cannot borrow here
ad.commit();
println!("{}", ad.data.a);
}
This snippet does not compile. The problem with the code snippet is that since Adder is borrowing data as mutable, upon the creation of Adder struct, all other immutable borrows (e.g.Holder here) could not live past. However, the add_once calls really do not need to borrow data mutably, only commit does that. Ideally there should be some way in rust to let the compiler know that add_once only needs added as mutable but not data, so that Holder could live past add_once calls but not commit call.
I do know two possible current solutions to this, however I think they both have drawbacks:
- I could wrap fields of
AdderinRefCelland borrow entireAdderimmutably, only borrows mutably when needed. The problem with this approach is thatRefCellchecks borrowing dynamically, when in this case it really does not need to be that way since all lifetime bounds should be known at compile time, just different parts of a struct could have different bounds. - I could do
unsafeblocks to convert references to raw pointers and do whatever I want. However as far as I think about it I could not find a way to do it without disable all borrow checks altogether (I don't really know how to do unsafe rust properly). And I want a borrow check so that ifHolderlives pastcommitthe compiler would still give a proper borrow error
So summary of questions:
- Is there a static and safe way in rust to borrow different parts of self, with different mutability?
- If not, is there a unsafe boilerplate to do so, still ensuring the proper borrow check as described above? (and if there is an existing crate for this)
- What is the best practice in general for these situations?
- Is there any RFC addressing this issue from rust language level?