I started off learning rust recently. Most of the docs / articles / posts about various rust internal mutability mechanisms either directly say "RefCell is bad" or make indirect statements like "if your code has RefCell, maybe you should rethink your design etc..". As and when I understand more of Rust and write more Rust code, I cant understand the basis for such defamation of RefCell. And no one says anything bad about Arc<Mutex<>> in any of the docs or articles, and from my perspective, it has the same problems as a RefCell (explained later below). If anyone can clarify whether thats just a myth or a fact and if so why, that would give some peace of mind :).
From my perspective, the most common / frequent / practical / much-needed use case for a RefCell is as below. Lets say I have a graph structure as below for example, holding a root node and some meta data about the graph.
mod graph:
// Graph is public
pub struct Graph {
root: Rc<RefCell<Node>>,
depth: usize
}
impl Graph {
pub fn add(&mut self, value: usize) {
let r = self.root.clone().borrow_mut();
r.add(value);
}
}
// Node is private
struct Node {
children: Vec<Rc<RefCell<Node>>>
}
impl Node {
fn add(&mut self, value: usize) {
}
}
mod graph_user:
let graph = Arc::new(Mutex::new(Graph { ... }));
let graph = graph.lock().unwrap();
graph.add(100);
So the graph Node is NEVER exposed to anyone for direct use, the Node and its functions can only be used once the Graph structure itself is locked. So that means, the node is effectively accessed in a single threaded environment. Now in this case, if I were to need a mutable access to Node, what other tool do I have at disposal, what other design pattern can I choose that will avoid this.
Yes I can replace Rc<RefCell<>> with a Arc<Mutex<>> and that will probably "look" like a nice design satisfying the docs and articles, but for one its a wasted additional mutex locking AND it has the same end problems as a RefCell - if used badly, a RefCell panics, and if used badly in this example, an Arc<Mutex<>> deadlocks - now the question is whether a panic is better or a deadlock is better :).
For example, the below code obviously deadlocks, and if its a RefCell instead it will panic - both are "run time" detection of errors and not "static" detections. So back to the questions - whats wrong with using a RefCell in this situation, what else can one use if not RefCell.
struct Foo {}
fn foo(a: Arc<Mutex<Foo>>) {
a.lock().unwrap();
}
fn main() {
let a = Arc::new(Mutex::new(Foo{}));
let b = a.lock().unwrap();
foo(a.clone());
}