1

I need to to iterate over a field of a struct inside Rc RefCell and modify some of its arguments according to its other field. For example for the struct Foo:

pub struct Foo {
    pub foo1: Vec<bool>,
    pub foo2: Vec<i32>,
}

The following code panics:

fn main() {
    let foo_cell = Rc::new(RefCell::new(Foo { foo1: vec![true, false], foo2: vec![1, 2] }));
    foo_cell.borrow_mut().foo2.iter_mut().enumerate().for_each(|(idx, foo2)| {
        if foo_cell.borrow().foo1[idx] {
            *foo2 *= -1;
        }
    });
}

I can solve it by cloning foo1 before the mutable borrow and just using the clone inside the closure but that leads to unnecessary copying. In fact I basically need one mutable reference to foo2 and one immutable reference to foo1 (Please note this is simplified code and zipping the iterators won't do here). I understand the code doesn't comply with borrowing rules.. Is there a way to get this to work without cloning data?

Adam
  • 743
  • 1
  • 6
  • 11
  • 1
    Does this answer your question? [Error while trying to borrow 2 fields from a struct wrapped in RefCell](https://stackoverflow.com/questions/47060266/error-while-trying-to-borrow-2-fields-from-a-struct-wrapped-in-refcell) – Sven Marnach Apr 09 '21 at 18:32

1 Answers1

2

The problem is that you are trying to reborrow the RefCell on each loop iteration while it has already been borrowed to get the iterator over foo2. The solution for this is to use a single foo_cell.borrow_mut() before the loop and get separate borrows to foo1 and foo2.

Note, the dereference on foo_cell.borrow_mut() is required to make this work.

use std::rc::Rc;
use std::cell::RefCell;

pub struct Foo {
    pub foo1: Vec<bool>,
    pub foo2: Vec<i32>,
}
fn main() {
    let foo_cell = Rc::new(RefCell::new(Foo {
        foo1: vec![true, false],
        foo2: vec![1, 2]
        
    }));
    // The dereference is required to get &mut Foo out of the RefMut<Foo>
    let borrow = &mut *foo_cell.borrow_mut();
    let foo1 = &borrow.foo1;

    borrow.foo2.iter_mut().enumerate().for_each(|(idx, foo2)| {
        if foo1[idx] {
            *foo2 *= -1;
        }
    });
}

Playground

sebpuetz
  • 2,430
  • 1
  • 7
  • 15
  • 1
    The crucial part is the explicit reborrow `&mut *`. The return value of `borrow_mut()` isn't just a reference, it's a `RefMut` struct that implements `Deref` and `DerefMut`. Everytime you access a field in the struct, the `deref()` and `deref_mut()` methods are called, which are opaque for the borrow checker, so it has to tag the whole struct as borrowed. Once you extract a single mutable reference to the whole struct, the borrow checker can see that you are accessing separate fields of the struct, since no opaque method calls are involved anymore. – Sven Marnach Apr 09 '21 at 18:35
  • 1
    It doesn't work to eliminate both variable assignments and inline both borrows https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9825e5c3318fd2810ec86307400a632a So I guess the borrow checker actually needs the bit of extra help to assign `let foo1 = &borrow.foo1;` outside of the closure. The mutable borrow can in fact be inlined as `borrow.foo2.iter_mut()...`. – sebpuetz Apr 09 '21 at 18:43
  • Thanks for the explanation, that worked. I kinda expected Ref and RefMut to work like regular references but I guess that's not the case – Adam Apr 10 '21 at 09:23
  • 1
    You can't inline both borrows because of the way variable capturing in closures work. Referencing `&borrow.foo1` inside the closure makes the closure capture `borrow` instead of just `borrow.foo1`. There is an [accepted RFC](https://github.com/rust-lang/rfcs/blob/master/text/2229-capture-disjoint-fields.md) to make this work at some point in the future. – Sven Marnach Apr 11 '21 at 11:04