struct Data {
...
}
struct SharedData{
shared: Rc<RefCell<Data>>
}
and I have got call stack, f1 calls f2, where each wants to access shared and mutate it, like this:
impl SharedData {
fn f1(&self) {
let data: &mut Data = &mut self.shared.borrow_mut();
// do something with data
data.field = ....
// call f2
self.f2()
// do something more with data
data.field = ....
}
fn f2(&self) {
let data: &mut Data = &mut self.shared.borrow_mut(); // here it is already taken in f1
// do something with data
data.field = ....
}
}
To solve the problem, I am wrapping access to shared in blocks, like this:
fn f1(&self) {
let intermediate_result = {
let data: &mut Data = &mut self.shared.borrow_mut();
// do something with data
data.field = ....
}
// call f2
self.f2()
{
let data: &mut Data = &mut self.shared.borrow_mut(); // take it again
// do something more with data
data.field = ....
}
}
but find it very ugly and not convenient and dangerous as errors are spotted only at runtime.
Another alternative, I looked at, is to move f1 and f2 methods impl Data, where borrowing would not be required as self of Data would be already in the scope. However, it also does not work for me because f1 and f2 spawn async tasks and I need to clone access to Data via Rc to do following up actions on futures then callbacks.
What is the most effective way to deal with this type of a constraint in Rust?
After experimenting for few hours, I have found that the safest and most elegant way to express this is to move f1 and f2 to impl Data and change their signature to have access to self (Data) and SharedData for async code. Something like this:
impl Data { // moved out of SharedData
fn f1(&self, shared_self: SharedData) {
let data = self;
// do something with data
data.field = ....
// call f2
data.f2(shared_self.clone())
// do something more with data
data.field = ....
// and still shared_self can be used in async code, like:
// let data: &mut Data = &mut shared_self.shared.borrow_mut();
}
fn f2(&self, shared_self: SharedData) {
let data = self; // works and not collision on nested borrowing
// do something with data
data.field = ....
// and still shared_self can be used in async code, like:
// let data: &mut Data = &mut shared_self.shared.borrow_mut();
}
}
I hope it helps somebody. It could be useful to capture this pattern to a collection of Effective Rust, if one exists ?
Approaches to things like this are discussed in this other thread. I feel like I’ve been linking to it a bit much lately but I’d have nothing new to say here .
Using explicit field borrows and associated functions (rather than methods) will make things easier.