Sling Academy
Home/Rust/Interior Mutability with RefCell and Cell in Rust

Interior Mutability with RefCell and Cell in Rust

Last updated: January 03, 2025

Understanding the concept of interior mutability in Rust is central to utilizing some of the language's more advanced features effectively. Rust, famous for its safety guarantees provided by strict ownership and borrowing rules, often requires breaking these rules under controlled circumstances, such as shared mutability. This is where types like RefCell and Cell come into play, offering a mechanism for interior mutability — a design pattern that allows you to mutate data even when there are immutable references to it without compromising its safety.

Understanding Interior Mutability

Interior mutability is the idea that a value can be mutable even if it appears immutable from the outside. This can be incredibly useful in scenarios like implementing the Observer pattern, dependency injection, or any other pattern where global mutable state was formerly the norm.

Cell

Cell provides interior mutability by moving values. It is suitable when accessing a copy of the data is sufficient, which is most practical for types that implement the Copy trait, like primitive types (i32, bool, etc.).

use std::cell::Cell;

fn main() {
    let cell = Cell::new(5);
    println!("Initial value: {}", cell.get());

    cell.set(10); // You can set a new value
    println!("New value: {}", cell.get());
}

Here, even though Cell is immutable, the Cell::set method allows us to mutate its value because it's designed to allow for interior mutability.

RefCell

RefCell is used when you need interior mutability for types that cannot implement Copy. It provides a runtime check to ensure certain invariants are met - specifically, Rust's borrowing rules around having either one mutable reference or any number of immutable references.

use std::cell::RefCell;

struct MyStruct {
    value: RefCell<i32>,
}

fn main() {
    let my_struct = MyStruct { value: RefCell::new(42) };

    // Borrow the value immutably
    let borrowed_value = my_struct.value.borrow();
    println!("Borrowed value: {}", *borrowed_value);

    // Borrow the value mutably
    {
        let mut borrowed_value_mut = my_struct.value.borrow_mut();
        *borrowed_value_mut = 45;
    } // Mutable borrow ends here

    // Borrow again immutably
    let borrowed_value_again = my_struct.value.borrow();
    println!("Borrowed value again: {}", *borrowed_value_again);
}

In this example, you can see how RefCell ensures that we adhere to Rust’s borrowing rules even at runtime. If the borrowing rules are violated with RefCell, the program will panic.

Difference Between Cell and RefCell

1. Cell:

  • Used with Copy types requiring neither mutable nor mutable data borrowing.
  • It is simpler than RefCell and cannot be used to generate panic due to violations during borrow checking in runtime.

 

2. RefCell:

  • Designed for non-Copy type data values.
  • Enforces Rust's ownership and borrowing rules during runtime through checks on borrowed references.
  • Panic occurs when rules are violated, i.e., two mutable borrows or simultaneously mutable and immutable borrows.

 

Conclusion

Interior mutability provided by Cell and RefCell is a powerful feature of Rust allowing the circumvention of restrictive borrowing designs in unique circumstances. While they help accomplish particular goals, they're more of an exception than a rule, used strategically to harness Rust's safety alongside more dynamic borrowing and mutation capabilities when the program logic absolutely needs it.

Next Article: Implementing Drop: Custom Cleanup Logic in Rust

Previous Article: Choosing Rc vs Arc for Shared Ownership in Different Contexts

Series: Ownership in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior