Rc
, RefCell
Sometimes Rust programs are complex and use a struct
instance in many places. This pattern calls for reference-counting—there are many owners of the struct
.
With Rc
, we can use a struct
throughout a program, and with RefCell
, we can specify that the struct
can be mutated. We call borrow_mut()
to modify the struct
.
Rc
exampleConsider this example code—it has a Bird struct
and a House struct
. The Bird is stored inside the House. But the Bird will be used elsewhere in the program.
Rc
around the Bird. And because the Bird may be modified, we place it in a RefCell
.Rc
and RefCell
are the single-threaded equivalents of Arc
and Mutex
. They have a similar purpose of enabling multiple uses of data.use std::cell::RefCell; use std::rc::Rc; struct Bird { feathers: i32 } struct House { bird_in_house: Rc<RefCell<Bird>> } fn main() { // Put reference-counted bird in the house. // ... Use RefCell in the Rc to allow the bird to be mutated. let h = House { bird_in_house: Rc::new(RefCell::new(Bird { feathers: 10 }))}; let mut b = h.bird_in_house.borrow_mut(); b.feathers += 1; println!("FEATHERS: {}", b.feathers); }FEATHERS: 11
RefCell
exampleWe can use RefCell
without an Rc
. With RefCell
, we can pass a struct
as a reference, and then gain write access to a field on the struct
.
struct
. In main, we pass a Container instance as a reference to the add()
function.add()
we use borrow_mut
on the RefCell
field. We can then mutate the field (we push to the vector).borrow()
on the RefCell
to read the value. In this way, we have read and write access to a field on a struct
.use std::cell::*; struct Container { items: RefCell<Vec<i32>>, } fn add(container: &Container) { // We can borrow the items and mutate from behind a reference. let mut temp = container.items.borrow_mut(); temp.push(10); } fn main() { let container = Container { items: RefCell::new(vec![]), }; // Pass as reference. add(&container); // We can access the items with borrow. println!("{:?}", container.items.borrow()); }[10]
RefCell
performanceIs there any measurable slowdown caused by using RefCell
over using a mutable reference to the containing struct
? This benchmark aims to find out.
borrow_mut()
on a RefCell
located in a struct
(contained in a vector of 1000 elements).Vec
as a mutable reference and no RefCells
are used.borrow_mut()
on RefCell
fields.use std::cell::*; use std::time::*; struct Test1 { count: RefCell<usize>, } struct Test2 { count: usize, } fn main() { let mut temp1 = vec![]; for _ in 0..1000 { temp1.push(Test1 { count: RefCell::new(0), }); } let mut temp2 = vec![]; for _ in 0..1000 { temp2.push(Test2 { count: 0 }); } if let Ok(max) = "100000000".parse::<usize>() { // Version 1: use RefCell. let t0 = Instant::now(); for i in 0..max { let mut c = temp1[i % 1000].count.borrow_mut(); *c += 1; } println!("{} ms", t0.elapsed().as_millis()); // Version 2: use mutable reference of struct. let t1 = Instant::now(); for i in 0..max { let c = &mut temp2[i % 1000].count; *c += 1; } println!("{} ms", t1.elapsed().as_millis()); } }95 ms RefCell borrow_mut() 73 ms &mut
It is possible to place all a program's mutable data in a single struct
, and avoid Rc
and Arc
. The struct
can be passed around the program's functions.
Rc
and Arc
, we can use a more object-based design where functions can just act upon the structs they need to know about.Rc
and RefCell
seem to have minimal impact. You can nest many structs inside a single Rc
, reducing the cost further.Rc
and RefCell
enable a struct
to be used in many places freely in a program with no concerns about ownership. Programs are easier to design and maintain this way.