Dandry
Dandry

Reputation: 515

Is there any way to borrow a RefCell immutably and mutably at the same time?

I have a piece of code which needs to operate on a list. This list contains items which come from another source and need to be processed and eventually removed. The list is also passed along to multiple functions which decide whether to add or remove an item. I created an example code which reflects my issue:

use std::{cell::RefCell, rc::Rc};

pub fn foo() {
    let list: Rc<RefCell<Vec<Rc<RefCell<String>>>>> = Rc::new(RefCell::new(Vec::new()));

    list.borrow_mut()
        .push(Rc::new(RefCell::new(String::from("ABC"))));

    while list.borrow().len() > 0 {
        let list_ref = list.borrow();

        let first_item = list_ref[0].borrow_mut();
        //item processing, needed as mutable

        list.borrow_mut().remove(0);
    }
}

This panics at runtime:

thread 'main' panicked at 'already borrowed: BorrowMutError', src/libcore/result.rs:997:5

I think I understand the problem: I have two immutable borrows and then a third which is mutable. According to the Rust docs, this is not allowed: either many immutable borrows or a single mutable one. Is there any way to get around this issue?

Upvotes: 2

Views: 2116

Answers (1)

Peter Varo
Peter Varo

Reputation: 12150

I have no idea what you are actually trying to achieve as you have failed to provide a minimal reproducible example, but I think you just mixed up the borrows of the list and the item in your data structure and that confused you in the first place.

Nonetheless the following code (which you can run in the playground) does what you have described above.

use std::{cell::RefCell, rc::Rc};

pub fn foo() {
    let list = Rc::new(RefCell::new(Vec::new()));
    let mut list = list.borrow_mut();

    let item = Rc::new(RefCell::new(String::from("ABC")));

    list.push(item);
    println!("list: {:?}", list);

    while let Some(item) = list.pop() {
        println!("item: {:?}", item);
        item.borrow_mut().push_str("DEF");
        println!("item: {:?}", item);
    }

    println!("list: {:?}", list);
}

fn main() {
    foo();
}

There are two tricks which I used here.

  1. I borrowed the list only once and that borrow was a mutable one, which allowed me to add and remove items from it.

  2. Because your description said you want to remove the items from the list anyway, I was able to iterate over the Vec with the pop or the remove methods (depending on the order you wish to get the items from the list). This means I didn't have to borrow the Vec for the scope of the loop (which you would otherwise do if you would iterate over it).

There are other ways to remove an element based on some predicate. For example: Removing elements from a Vec based on some condition.

To actually answer your original question: there is no way to have an immutable and a mutable borrow at the same time safely. That is one of the core principles of Rust which makes it memory safe. Think about it, what kind of guarantee would immutability be if at the same time, under the hood, the data could actually change?

Upvotes: 3

Related Questions