Rc<RefCell<T>> / Arc<Mutex<T>>
| Since: | Rust 1.0(2015) |
|---|
Rc<RefCell<T>> is a pattern for multiple ownership with interior mutability in a single-threaded context. Arc<Mutex<T>> is a pattern for thread-safe multiple ownership with interior mutability in a multi-threaded context.
Syntax
use std::rc::Rc;
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
// Single-threaded: multiple ownership + interior mutability
let shared = Rc::new(RefCell::new(value));
let clone = Rc::clone(&shared);
*shared.borrow_mut() = new_value;
// Multi-threaded: thread-safe multiple ownership + interior mutability
let shared = Arc::new(Mutex::new(value));
let clone = Arc::clone(&shared);
let mut guard = shared.lock().unwrap();
*guard = new_value;
Combination Overview
| Pattern | Thread | Description |
|---|---|---|
| Rc<RefCell<T>> | Single only | Reference-counted sharing with runtime borrow checking for mutability. |
| Arc<Mutex<T>> | Multi-thread | Atomic reference-counted sharing with exclusive access via Mutex. |
| Arc<RwLock<T>> | Multi-thread | Multiple threads can acquire a read-only lock simultaneously. |
Sample Code
sample_rc_refcell_arc_mutex.rs
use std::rc::Rc;
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Rc<RefCell<T>>: append to a shared list in a single thread.
let list = Rc::new(RefCell::new(vec![]));
let list_a = Rc::clone(&list);
let list_b = Rc::clone(&list);
list_a.borrow_mut().push(1);
list_b.borrow_mut().push(2);
list.borrow_mut().push(3);
println!("{:?}", list.borrow()); // [1, 2, 3]
// Arc<Mutex<T>>: share a counter across multiple threads.
let counter = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..5).map(|_| {
let cnt = Arc::clone(&counter);
thread::spawn(move || {
let mut n = cnt.lock().unwrap();
*n += 1; // Acquire the Mutex lock before modifying the value.
})
}).collect();
for h in handles {
h.join().unwrap();
}
println!("counter: {}", *counter.lock().unwrap()); // counter: 5
}
Compile with the following command:
rustc rc_refcell_arc_mutex.rs ./rc_refcell_arc_mutex [1, 2, 3] counter: 5
rc_refcell_arc_mutex2.rs
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let config = Arc::new(RwLock::new(vec!["Kogami Shinya", "Tsunemori Akane"]));
// Multiple threads can read simultaneously.
let readers: Vec<_> = (0..3).map(|i| {
let cfg = Arc::clone(&config);
thread::spawn(move || {
let data = cfg.read().unwrap(); // Acquire a read lock.
println!("reader {}: {:?}", i, *data);
})
}).collect();
for h in readers {
h.join().unwrap();
}
// Writing requires an exclusive lock.
{
let mut data = config.write().unwrap();
data.push("Ginoza Nobuchika");
println!("Write complete: {:?}", *data);
}
}
Compile with the following command:
rustc rc_refcell_arc_mutex2.rs ./rc_refcell_arc_mutex2 reader 0: ["Kogami Shinya", "Tsunemori Akane"] reader 1: ["Kogami Shinya", "Tsunemori Akane"] reader 2: ["Kogami Shinya", "Tsunemori Akane"] Write complete: ["Kogami Shinya", "Tsunemori Akane", "Ginoza Nobuchika"]
rc_refcell_arc_mutex3.rs
use std::rc::Rc;
use std::cell::RefCell;
type NodeRef = Rc<RefCell<Node>>;
struct Node {
value: i32,
children: Vec<NodeRef>,
}
impl Node {
fn new(value: i32) -> NodeRef {
Rc::new(RefCell::new(Node { value, children: vec![] }))
}
fn add_child(parent: &NodeRef, child: NodeRef) {
parent.borrow_mut().children.push(child);
}
}
fn print_tree(node: &NodeRef, depth: usize) {
println!("{}{}", " ".repeat(depth), node.borrow().value);
for child in &node.borrow().children {
print_tree(child, depth + 1);
}
}
fn main() {
let root = Node::new(1);
let left = Node::new(2);
let right = Node::new(3);
let leaf = Node::new(4);
Node::add_child(&left, Rc::clone(&leaf));
Node::add_child(&root, Rc::clone(&left));
Node::add_child(&root, Rc::clone(&right));
print_tree(&root, 0);
}
Compile with the following command:
rustc rc_refcell_arc_mutex3.rs
./rc_refcell_arc_mutex3
1
2
4
3
Common Mistakes
Common Mistake 1: Double borrow_mut on RefCell causes panic
Acquiring multiple borrow_mut() calls in the same scope causes a runtime panic. This is not a compile error, so care is needed.
rc_refcell_mistake1.rs
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
let mut v1 = data.borrow_mut();
// Calling borrow_mut again while the first mutable borrow is still held.
let mut v2 = data.borrow_mut(); // Runtime panic.
v1.push(4);
v2.push(5);
}
Compile with the following command:
rustc rc_refcell_mistake1.rs ./rc_refcell_mistake1 thread 'main' panicked at 'already mutably borrowed: BorrowMutError'
Separate the borrow_mut scopes or use try_borrow_mut().
rc_refcell_fix1.rs
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
{
let mut v = data.borrow_mut();
v.push(4);
} // Dropped here.
{
let mut v = data.borrow_mut();
v.push(5);
}
println!("{:?}", data.borrow()); // [1, 2, 3, 4, 5]
}
Compile with the following command:
rustc rc_refcell_fix1.rs ./rc_refcell_fix1 [1, 2, 3, 4, 5]
Common Mistake 2: Inconsistent Mutex lock order causes deadlock
When two Mutexes are locked in different orders, thread A may hold lock1 and wait for lock2 while thread B holds lock2 and waits for lock1, causing a deadlock.
rc_refcell_mutex_note.rs
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let lock1 = Arc::new(Mutex::new(0));
let lock2 = Arc::new(Mutex::new(0));
// Acquiring locks in the same order (lock1 then lock2) in all threads prevents deadlock.
let l1 = Arc::clone(&lock1);
let l2 = Arc::clone(&lock2);
let h1 = thread::spawn(move || {
let _a = l1.lock().unwrap(); // Acquire lock1 first.
let _b = l2.lock().unwrap(); // Then acquire lock2.
println!("thread 1: locked");
});
let l1 = Arc::clone(&lock1);
let l2 = Arc::clone(&lock2);
let h2 = thread::spawn(move || {
let _a = l1.lock().unwrap(); // Also acquire lock1 first here.
let _b = l2.lock().unwrap();
println!("thread 2: locked");
});
h1.join().unwrap();
h2.join().unwrap();
}
Compile with the following command:
rustc rc_refcell_mutex_note.rs ./rc_refcell_mutex_note thread 1: locked thread 2: locked
Overview
In Rust, no single smart pointer can satisfy the need to mutate a value from multiple owners, so combining smart pointers is a common pattern. For example, when you want to mutate a value shared via Rc<T>, you cannot use a regular &mut. This is exactly where RefCell<T>'s interior mutability is needed. Rc<RefCell<T>> is the standard choice for single-threaded code, while Arc<Mutex<T>> is the standard choice for multi-threaded code.
To prevent forgotten lock releases, Rust's Mutex is designed so that the guard (MutexGuard) automatically releases the lock when it goes out of scope.
Locking multiple Mutexes in different orders can cause a deadlock. When multiple locks are needed, acquiring them in a consistent order is necessary to avoid deadlocks.
If you find any errors or copyright issues, please contact us.