Loading page…
Rust walkthroughs
Loading page…
RefCell provides interior mutability for any type by enforcing Rust's borrowing rules at runtime instead of compile time. It allows you to mutate the inner value through a shared reference (&T), with runtime checks that panic if the rules are violated.
Key concepts:
T, not just Copy typesborrow() for &T, borrow_mut() for &mut TRefCell is not thread-safe (!Sync)When to use RefCell:
Copy types through shared referencesWhen NOT to use RefCell:
Mutex or RwLock)Cell for lower overhead)use std::cell::RefCell;
fn main() {
let ref_cell = RefCell::new(42);
// Immutable borrow
{
let value = ref_cell.borrow();
println!("Value: {}", value);
}
// Mutable borrow
{
let mut value = ref_cell.borrow_mut();
*value = 100;
}
println!("New value: {}", ref_cell.borrow());
}use std::cell::RefCell;
struct Counter {
count: RefCell<u32>,
}
impl Counter {
fn new() -> Self {
Self {
count: RefCell::new(0),
}
}
fn increment(&self) {
let mut count = self.count.borrow_mut();
*count += 1;
}
fn get(&self) -> u32 {
*self.count.borrow()
}
}
fn main() {
let counter = Counter::new();
// Multiple shared references can mutate
let ref1 = &counter;
let ref2 = &counter;
ref1.increment();
ref2.increment();
counter.increment();
println!("Count: {}", counter.get());
}use std::cell::RefCell;
struct Data {
name: RefCell<String>,
items: RefCell<Vec<i32>>,
}
impl Data {
fn new(name: &str) -> Self {
Self {
name: RefCell::new(String::from(name)),
items: RefCell::new(Vec::new()),
}
}
fn add_item(&self, item: i32) {
self.items.borrow_mut().push(item);
}
fn update_name(&self, new_name: &str) {
*self.name.borrow_mut() = String::from(new_name);
}
fn print(&self) {
println!("Name: {}", self.name.borrow());
println!("Items: {:?}", self.items.borrow());
}
}
fn main() {
let data = Data::new("test");
data.add_item(1);
data.add_item(2);
data.add_item(3);
data.update_name("updated");
data.print();
}use std::cell::RefCell;
fn main() {
let ref_cell = RefCell::new(42);
// Multiple immutable borrows - OK
{
let a = ref_cell.borrow();
let b = ref_cell.borrow();
println!("a: {}, b: {}", a, b);
// Both dropped here
}
// One mutable borrow - OK
{
let mut m = ref_cell.borrow_mut();
*m = 100;
}
// This would PANIC:
// let a = ref_cell.borrow();
// let mut m = ref_cell.borrow_mut(); // panic! - can't have mutable borrow while borrowed
// This would also PANIC:
// let mut m1 = ref_cell.borrow_mut();
// let mut m2 = ref_cell.borrow_mut(); // panic! - can't have multiple mutable borrows
println!("Final value: {}", ref_cell.borrow());
}use std::cell::RefCell;
use std::rc::Rc;
struct Node {
value: i32,
edges: RefCell<Vec<Rc<Node>>>,
}
impl Node {
fn new(value: i32) -> Rc<Self> {
Rc::new(Self {
value,
edges: RefCell::new(Vec::new()),
})
}
fn connect(&self, other: Rc<Node>) {
self.edges.borrow_mut().push(other);
}
fn neighbors(&self) -> Vec<i32> {
self.edges.borrow().iter().map(|n| n.value).collect()
}
}
fn main() {
let a = Node::new(1);
let b = Node::new(2);
let c = Node::new(3);
a.connect(b.clone());
a.connect(c.clone());
b.connect(a.clone());
println!("Node {} neighbors: {:?}", a.value, a.neighbors());
println!("Node {} neighbors: {:?}", b.value, b.neighbors());
}use std::cell::RefCell;
use std::rc::{Rc, Weak};
struct Node {
value: i32,
next: RefCell<Option<Rc<Node>>>,
prev: RefCell<Option<Weak<Node>>>,
}
impl Node {
fn new(value: i32) -> Rc<Self> {
Rc::new(Self {
value,
next: RefCell::new(None),
prev: RefCell::new(None),
})
}
}
struct DoublyLinkedList {
head: Option<Rc<Node>>,
}
impl DoublyLinkedList {
fn new() -> Self {
Self { head: None }
}
fn push_front(&mut self, value: i32) {
let new_node = Node::new(value);
if let Some(old_head) = self.head.take() {
*new_node.next.borrow_mut() = Some(old_head.clone());
*old_head.prev.borrow_mut() = Some(Rc::downgrade(&new_node));
}
self.head = Some(new_node);
}
fn print_forward(&self) {
let mut current = self.head.clone();
print!("Forward: ");
while let Some(node) = current {
print!("{} ", node.value);
current = node.next.borrow().clone();
}
println!();
}
}
fn main() {
let mut list = DoublyLinkedList::new();
list.push_front(3);
list.push_front(2);
list.push_front(1);
list.print_forward();
}use std::cell::RefCell;
fn main() {
let ref_cell = RefCell::new(42);
// try_borrow returns Result instead of panicking
let borrow1 = ref_cell.borrow();
match ref_cell.try_borrow() {
Ok(borrow2) => println!("Borrow succeeded: {}", borrow2),
Err(e) => println!("Borrow failed: {}", e),
}
// try_borrow_mut also returns Result
match ref_cell.try_borrow_mut() {
Ok(_) => println!("Mutable borrow succeeded"),
Err(e) => println!("Mutable borrow failed: {}", e),
}
drop(borrow1);
// Now mutable borrow will succeed
match ref_cell.try_borrow_mut() {
Ok(mut borrow) => {
*borrow = 100;
println!("Modified value: {}", borrow);
}
Err(e) => println!("Failed: {}", e),
}
}use std::cell::RefCell;
struct Cache<T> {
value: RefCell<Option<T>>,
compute_fn: fn() -> T,
}
impl<T> Cache<T> {
fn new(compute_fn: fn() -> T) -> Self {
Self {
value: RefCell::new(None),
compute_fn,
}
}
fn get(&self) -> T
where
T: Clone,
{
let value = self.value.borrow();
if let Some(v) = value.as_ref() {
return v.clone();
}
drop(value); // Release borrow before mutation
let computed = (self.compute_fn)();
*self.value.borrow_mut() = Some(computed.clone());
computed
}
fn invalidate(&self) {
*self.value.borrow_mut() = None;
}
}
fn main() {
let cache = Cache::new(|| {
println!("Computing expensive value...");
42
});
println!("First get: {}", cache.get());
println!("Second get: {}", cache.get());
cache.invalidate();
println!("After invalidate: {}", cache.get());
}use std::cell::RefCell;
use std::rc::Rc;
trait Observer {
fn update(&self, message: &str);
}
struct Subject {
observers: RefCell<Vec<Rc<dyn Observer>>>,
}
impl Subject {
fn new() -> Self {
Self {
observers: RefCell::new(Vec::new()),
}
}
fn attach(&self, observer: Rc<dyn Observer>) {
self.observers.borrow_mut().push(observer);
}
fn notify(&self, message: &str) {
for observer in self.observers.borrow().iter() {
observer.update(message);
}
}
}
struct PrintObserver {
name: String,
}
impl Observer for PrintObserver {
fn update(&self, message: &str) {
println!("[{}] Received: {}", self.name, message);
}
}
fn main() {
let subject = Subject::new();
let observer1 = Rc::new(PrintObserver { name: String::from("Observer1") });
let observer2 = Rc::new(PrintObserver { name: String::from("Observer2") });
subject.attach(observer1);
subject.attach(observer2);
subject.notify("Hello, World!");
}use std::cell::RefCell;
struct Config {
settings: RefCell<Settings>,
}
#[derive(Clone)]
struct Settings {
debug: bool,
timeout_ms: u64,
max_retries: u32,
}
impl Config {
fn new() -> Self {
Self {
settings: RefCell::new(Settings {
debug: false,
timeout_ms: 1000,
max_retries: 3,
}),
}
}
fn update<F>(&self, f: F)
where
F: FnOnce(&mut Settings),
{
f(&mut self.settings.borrow_mut());
}
fn get(&self) -> Settings {
self.settings.borrow().clone()
}
}
fn main() {
let config = Config::new();
// Update through shared reference
config.update(|s| {
s.debug = true;
s.timeout_ms = 5000;
});
let settings = config.get();
println!("Debug: {}, Timeout: {}ms", settings.debug, settings.timeout_ms);
}use std::cell::{Cell, RefCell};
fn main() {
// Cell: Only for Copy types, no references
let cell: Cell<i32> = Cell::new(42);
cell.set(100);
let value = cell.get();
// RefCell: Any type, with references
let ref_cell: RefCell<String> = RefCell::new(String::from("hello"));
// Can get reference to inner value
{
let r = ref_cell.borrow();
println!("Borrowed: {}", r);
// Can read r.len(), r.chars(), etc.
}
// Can get mutable reference
{
let mut m = ref_cell.borrow_mut();
m.push_str(" world");
}
println!("After mutation: {}", ref_cell.borrow());
}use std::cell::RefCell;
use std::sync::{Arc, Mutex};
fn main() {
// RefCell: Single-threaded, panics on borrow violation
let ref_cell = RefCell::new(42);
{
let _borrow = ref_cell.borrow();
// let _mut = ref_cell.borrow_mut(); // Would PANIC
}
// Mutex: Multi-threaded, blocks on contention
let mutex = Arc::new(Mutex::new(42));
{
let _lock = mutex.lock().unwrap();
// let _lock2 = mutex.lock().unwrap(); // Would BLOCK (same thread: deadlock)
}
println!("RefCell: {}", ref_cell.borrow());
println!("Mutex: {}", mutex.lock().unwrap());
}use std::cell::RefCell;
struct LazyValue<T> {
value: RefCell<Option<T>>,
init: fn() -> T,
}
impl<T> LazyValue<T> {
fn new(init: fn() -> T) -> Self {
Self {
value: RefCell::new(None),
init,
}
}
fn get(&self) -> Ref<'_, T>
where
T: Clone,
{
let mut value = self.value.borrow_mut();
if value.is_none() {
*value = Some((self.init)());
}
drop(value);
Ref::map(self.value.borrow(), |v| v.as_ref().unwrap())
}
}
use std::cell::Ref;
fn main() {
let lazy = LazyValue::new(|| {
println!("Initializing...");
42
});
println!("First access: {}", lazy.get());
println!("Second access: {}", lazy.get());
}use std::cell::RefCell;
struct Document {
content: RefCell<String>,
history: RefCell<Vec<String>>,
}
impl Document {
fn new() -> Self {
Self {
content: RefCell::new(String::new()),
history: RefCell::new(Vec::new()),
}
}
fn write(&self, text: &str) {
// Save current state
self.history.borrow_mut().push(self.content.borrow().clone());
self.content.borrow_mut().push_str(text);
}
fn undo(&self) -> bool {
if let Some(previous) = self.history.borrow_mut().pop() {
*self.content.borrow_mut() = previous;
true
} else {
false
}
}
fn content(&self) -> String {
self.content.borrow().clone()
}
}
fn main() {
let doc = Document::new();
doc.write("Hello");
doc.write(", World");
doc.write("!");
println!("Content: {}", doc.content());
doc.undo();
println!("After undo: {}", doc.content());
doc.undo();
println!("After another undo: {}", doc.content());
}use std::cell::RefCell;
fn main() {
let ref_cell = RefCell::new(42);
// Check borrow state
println!("Borrows: {}", ref_cell.borrow_state().borrow_count());
{
let _borrow1 = ref_cell.borrow();
println!("After one borrow: {}", ref_cell.borrow_state().borrow_count());
{
let _borrow2 = ref_cell.borrow();
println!("After two borrows: {}", ref_cell.borrow_state().borrow_count());
}
println!("After dropping one: {}", ref_cell.borrow_state().borrow_count());
}
println!("After all dropped: {}", ref_cell.borrow_state().borrow_count());
}RefCell Methods:
| Method | Description | Panics? |
|--------|-------------|--------|
| new(value) | Create a new RefCell | No |
| borrow() | Get immutable reference (Ref<T>) | On conflict |
| borrow_mut() | Get mutable reference (RefMut<T>) | On conflict |
| try_borrow() | Try to get Ref<T>, returns Result | No |
| try_borrow_mut() | Try to get RefMut<T>, returns Result | No |
| into_inner() | Consume and return inner value | No |
| replace(value) | Replace value and return old | On borrow conflict |
| swap(&other) | Swap with another RefCell | On borrow conflict |
RefCell vs Alternatives:
| Type | Works With | Thread Safe | Error Handling |
|------|------------|-------------|----------------|
| Cell<T> | Copy types only | No | N/A |
| RefCell<T> | Any type | No | Panics |
| Mutex<T> | Any type | Yes | Blocks/poison |
| RwLock<T> | Any type | Yes | Blocks/poison |
When to Use RefCell:
| Scenario | Appropriate? |
|----------|-------------|
| Non-Copy type mutation through &T | ✅ Yes |
| Graph/linked list structures | ✅ Yes |
| Observer pattern | ✅ Yes |
| Lazy initialization | ✅ Yes |
| Testing and prototyping | ✅ Yes |
| High-performance production code | ⚠️ Consider carefully |
| Multi-threaded code | ❌ Use Mutex |
| Copy types | ❌ Use Cell (lower overhead) |
Key Points:
RefCell<T> provides interior mutability for any type!Sync)try_borrow()/try_borrow_mut() to avoid panicsCell<T> for Copy types (no runtime overhead)Mutex<T> for thread-safe interior mutabilityRef and RefMut are the smart pointer types returned