Error while trying to borrow 2 fields from a struct wrapped in RefCell
I have a struct which contains both data and a writer which will eventually be used to write the data. The struct is wrapped in a RefCell
. Here's a small reproduction:
use std::cell::RefCell;
use std::io::Write;
struct Data {
string: String,
}
struct S {
data: Data,
writer: Vec<u8>,
}
fn write(s: RefCell<S>) {
let mut mut_s = s.borrow_mut();
let str = &mut_s.data.string;
mut_s.writer.write(str.as_bytes());
}
The compiler is angry:
error[E0502]: cannot borrow `mut_s` as mutable because it is also borrowed as immutable
--> src\main.rs:16:5
|
15 | let str = &mut_s.data.string;
| ----- immutable borrow occurs here
16 | mut_s.writer.write(str.as_bytes());
| ^^^^^ mutable borrow occurs here
17 | }
| - immutable borrow ends here
Is there a different API I should use?
Solution 1:
You can manually invoke DerefMut
and then save the resulting reference:
fn write(s: RefCell<S>) {
let mut mut_s = s.borrow_mut();
let mut tmp = &mut *mut_s; // Here
let str = &tmp.data.string;
tmp.writer.write(str.as_bytes());
}
Or in one line:
fn write(s: RefCell<S>) {
let mut_s = &mut *s.borrow_mut(); // Here
let str = &mut_s.data.string;
mut_s.writer.write(str.as_bytes());
}
The problem is that borrow_mut
doesn't return your struct directly — it returns a RefMut
. Normally, this is transparent as this struct implements Deref
and DerefMut
, so any methods called on it are passed to the underlying type. The pseudo-expanded code looks something like this:
use std::cell::RefMut;
use std::ops::{Deref, DerefMut};
fn write(s: RefCell<S>) {
let mut mut_s: RefMut<S> = s.borrow_mut();
let str = &Deref::deref(&mut_s).data.string;
DerefMut::deref_mut(&mut mut_s).writer.write(str.as_bytes());
}
Rust doesn't track field-level borrows across function calls (even for Deref::deref
or DerefMut::deref_mut
). This causes your error, as the deref_mut
method would need to be called during the outstanding borrow from the previous Deref::deref
.
The expanded version with the explicit borrow looks like this, with a single call to Deref::deref_mut
:
use std::cell::RefMut;
use std::ops::DerefMut;
fn write(s: RefCell<S>) {
let mut mut_s: RefMut<S> = s.borrow_mut();
let tmp: &mut S = DerefMut::deref_mut(&mut mut_s);
let str = &tmp.data.string;
tmp.writer.write(str.as_bytes());
}
The compiler can then track that the two borrows from that temporary value are disjoint.
Note that this problem isn't unique to RefCell
! Any type that implements DerefMut
can experience the same problem. Here's some from the standard library:
Box
-
MutexGuard
(fromMutex
) -
PeekMut
(fromBinaryHeap
) -
RwLockWriteGuard
(fromRwLock
) String
Vec
Pin