Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error while trying to borrow 2 fields from a struct wrapped in RefCell

Tags:

rust

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?

like image 255
Shmoopy Avatar asked Nov 01 '17 17:11

Shmoopy


1 Answers

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 (from Mutex)
  • PeekMut (from BinaryHeap)
  • RwLockWriteGuard (from RwLock)
  • String
  • Vec
  • Pin
like image 80
Shepmaster Avatar answered Oct 19 '22 04:10

Shepmaster