capypad
0 day streak
rust / intermediate
Snippet

Interior Mutability with RefCell<T>

RefCell<T> provides interior mutability by moving borrow checking from compile time to runtime. While a &self reference normally prevents mutation, RefCell allows you to mutate the data it contains. The borrow_mut() method returns a mutable reference, and the RefCell tracks how many borrows are active. If you try to violate borrow rules at runtime (e.g., having two mutable borrows), the program will panic. This pattern is useful when you need to mutate data in scenarios where immutability is expected, such as in cached computations or when implementing data structures that require mutable access to their own data.

snippet.rs
rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
use std::cell::RefCell;
 
struct Logger {
messages: RefCell<Vec<String>>,
max_size: usize,
}
 
impl Logger {
fn new(max_size: usize) -> Self {
Self {
messages: RefCell::new(Vec::new()),
max_size,
}
}
 
fn log(&self, msg: &str) {
let mut messages = self.messages.borrow_mut();
if messages.len() >= self.max_size {
messages.remove(0);
}
messages.push(msg.to_string());
}
 
fn get_messages(&self) -> Vec<String> {
self.messages.borrow().clone()
}
}
 
fn main() {
let logger = Logger::new(3);
logger.log("Application started");
logger.log("User logged in");
logger.log("Data loaded");
logger.log("Request processed");
 
println!("Logged messages: {:?}", logger.get_messages());
}
Breakdown
1
use std::cell::RefCell;
Import RefCell from the std::cell module, which provides interior mutability primitives
2
struct Logger {
Define a Logger struct that will hold changeable data behind an immutable interface
3
messages: RefCell<Vec<String>>,
Use RefCell to wrap the Vec, enabling mutation through shared references
4
max_size: usize,
Store maximum capacity for the log buffer
5
fn new(max_size: usize) -> Self {
Constructor creates a new Logger with specified buffer size
6
messages: RefCell::new(Vec::new()),
Initialize the RefCell with an empty Vec inside
7
fn log(&self, msg: &str) {
Method takes immutable self but can mutate internal data thanks to RefCell
8
let mut messages = self.messages.borrow_mut();
Acquire mutable borrow of the inner Vec; this is where runtime borrow checking occurs
9
if messages.len() >= self.max_size {
Check if buffer has reached maximum capacity
10
messages.remove(0);
Remove oldest message when at capacity (FIFO behavior)
11
messages.push(msg.to_string());
Add new message to the end of the buffer
12
fn get_messages(&self) -> Vec<String> {
Getter method to retrieve all logged messages
13
self.messages.borrow().clone()
Immutable borrow, clone to return owned data