In Rust, RefCell is a type that provides interior mutability, allowing you to mutate data even when you have a shared reference to it. It enforces Rust’s borrowing rules at runtime instead of compile time.

Normally Rust’s borrow checker enforce statically that only one mutable reference (&mut) exists at a time, or multiple immutable references (&) exists but no mutable references. The same rules are enforced by RefCell at runtime, which will cause the program to panic if they are violated.

Tip

Rust also has a Cell types that works only on type that implements the Copy trait, so it allows to update the value directly without involving references and has no borrow checking therefore

Use cases

Interior mutability

When you want to expose an immutable structure or interface to the outside world, while having internal mutable state, you can use a private struct field of type Refcell

use std::cell::RefCell;
 
struct Counter {
    count: RefCell<u32>,
}
 
impl Counter {
    fn new() -> Self {
        Counter {
            count: RefCell::new(0),
        }
    }
 
    fn increment(&self) {
        *self.count.borrow_mut() += 1;
    }
 
    fn get_count(&self) -> u32 {
        *self.count.borrow()
    }
}
 
fn main() {
    let counter = Counter::new();
    counter.increment();
    counter.increment();
    println!("Counter: {}", counter.get_count());
}

Output:

Counter: 2

Shared mutability

RefCell is often used with Rc for shared ownership of mutable data.

Example: Shared Mutability with Rc + RefCell

use std::cell::RefCell;
use std::rc::Rc;
 
fn main() {
    let shared_value = Rc::new(RefCell::new(42));
 
    let clone1 = Rc::clone(&shared_value);
    let clone2 = Rc::clone(&shared_value);
 
    {
        let mut borrowed_value = clone1.borrow_mut();
        *borrowed_value += 8;
    }
 
    println!("Updated value: {}", *clone2.borrow());
}

Output:

Updated value: 50

RefCell in Asynchronous Contexts

For async scenarios, Rust provides tokio::sync::Mutex and async_std::sync::RwLock for interior mutability in concurrent contexts. These types are async-safe alternatives to RefCell.

Why Not Use RefCell in Async?

RefCell is not thread-safe. If you need interior mutability across threads or async tasks, use thread-safe types like:

  • Mutex: For mutual exclusion (single writer at a time).
  • RwLock: For multiple readers or a single writer.

Example: Async Alternative

use tokio::sync::Mutex;
use std::sync::Arc;
 
#[tokio::main]
async fn main() {
    let shared_value = Arc::new(Mutex::new(5));
 
    let clone = Arc::clone(&shared_value);
    tokio::spawn(async move {
        let mut value = clone.lock().await;
        *value += 1;
    }).await.unwrap();
 
    let value = shared_value.lock().await;
    println!("Updated value: {}", *value);
}