Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
63: Interior Mutability Patterns
I was working on a small game project the other day, and I ran into a classic Rust frustration. I had a Player struct and an AchievementSystem. The achievement system needs to hold a reference to the player to monitor their progress, but it also needs to be able to update a "bonus" flag on that player when certain conditions are met.
At first, I did what most of us do: I tried to just pass a mutable reference. But then I realized the achievement system wasn't the only thing tracking the player; the renderer and the physics engine were also holding onto that player. Rust's borrow checker immediately stepped in and told me I couldn't have multiple mutable references to the same data. I was stuck.
Hitting the Borrow Checker Wall
Here is where I started. I wanted my AchievementSystem to be able to bump the player's score or set a flag, even though it only holds a shared reference to the player.
struct Player {
name: String,
score: u32,
}
struct AchievementSystem {
player: Player, // I'll just embed it for simplicity here
}
impl AchievementSystem {
fn check_milestone(&mut self) {
if self.player.score > 100 {
println!("Achievement Unlocked!");
// I want to reward them with bonus points here
self.player.score += 10;
}
}
}
This works fine if the AchievementSystem owns the player. But in a real game, the Player is owned by a World struct, and the AchievementSystem just has a reference to it. If I change player: Player to player: &Player, the line self.player.score += 10 triggers a compiler error: "cannot assign to data in a & reference".
I can't make the reference mutable (&mut Player) because then no one else—like the renderer—could even look at the player while the achievement system is holding it. This is the core conflict: I have a shared reference, but I need to mutate something inside it.
Moving the Borrow Checker to Runtime
This is where "Interior Mutability" comes in. The idea is to wrap the data we want to change in a container that handles the borrowing rules at runtime instead of compile time. I tried RefCell first.
I wrapped the score in a RefCell. This tells Rust: "I know this looks immutable from the outside, but trust me, I'll manage the borrowing safely inside."
use std::cell::RefCell;
struct Player {
name: String,
score: RefCell<u32>,
}
struct AchievementSystem<'a> {
player: &'a Player,
}
impl<'a> AchievementSystem<'a> {
fn check_milestone(&self) {
// Notice 'self' is now an immutable reference!
let mut score = self.player.score.borrow_mut();
if *score > 100 {
*score += 10;
println!("Bonus points added!");
}
}
}
This compiles! I'm using an immutable reference to the AchievementSystem, which holds an immutable reference to the Player, yet I'm still mutating the score. borrow_mut() is the magic here. It checks at runtime if anyone else is currently borrowing the value. If they aren't, it gives me a mutable reference.
The Price of Runtime Checks
But there's a catch. Since we moved the check from compile-time to runtime, we can now crash the program if we aren't careful. I tried to see what would happen if I borrowed the score twice in the same scope.
let score_ref1 = self.player.score.borrow();
let score_ref2 = self.player.score.borrow_mut(); // PANIC!
The program immediately panics with: "already borrowed: BorrowMutError". RefCell enforces the same rules as the borrow checker (either one mutable borrow OR many immutable borrows), but it does so by panicking instead of refusing to compile. It's a powerful tool, but it's essentially a "trust me" button that can blow up in your face if your logic is messy.
A Simpler Path for Small Data
As I was refining this, I realized the score is just a u32. Using RefCell feels like overkill because I'm creating a Ref object just to change a number. I remembered Cell.
Cell is different. It doesn't give you a reference to the inner value; instead, it just replaces the value entirely. For Copy types like integers, this is much cleaner and faster because it doesn't need to track borrows at all—it just swaps the bits.
use std::cell::Cell;
struct Player {
name: String,
score: Cell<u32>,
}
impl<'a> AchievementSystem<'a> {
fn check_milestone(&self) {
let current_score = self.player.score.get();
if current_score > 100 {
self.player.score.set(current_score + 10);
}
}
}
No borrow_mut(), no risk of panicking, and no Ref objects. If you're dealing with simple Copy types, Cell is almost always the better choice. Use RefCell when you have complex types (like a Vec or a custom struct) that you need to mutate via a reference.
Scaling to Multiple Threads
Finally, I thought about what happens if I move this to a multi-threaded game loop. Cell and RefCell are not thread-safe (they don't implement Sync). If I try to share a RefCell across threads, the compiler will stop me dead.
The "Interior Mutability" pattern persists in multi-threaded code, but the tools change. Instead of RefCell, we use Mutex or RwLock. The logic is identical: you have a shared reference to a wrapper, and you ask that wrapper for mutable access to the guts. The only difference is that instead of panicking or swapping bits, they make the thread wait (block) until the data is available.
📋 Practical Task
Implementing a Thread-Safe Shared Configuration
You are building a server where multiple threads need to read a shared configuration, but a single "Admin" thread needs to be able to update the configuration settings on the fly without restarting the server.
Your Task:
- Create a
Configstruct containing aport: u16and alog_level: String. - Wrap this
Configin a way that allows it to be shared across multiple threads (usingArcand an interior mutability pattern). - Implement a function
update_port(config: &SharedConfig, new_port: u16)that updates the port. - Implement a function
read_port(config: &SharedConfig) -> u16that reads the current port. - Spawn two threads: one that periodically reads the port and prints it, and one that updates the port after a short delay.
Hint: Since this is multi-threaded, RefCell will not work. You will need to use Mutex or RwLock.
There are no comments for now.