-
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
141: Practice Exercise: Implementing a Graph with Rc<RefCell<>>
When you first try to build a graph in Rust, you're going to hit a wall. It’s almost a rite of passage. You'll likely start with something that feels intuitive—a Node struct that holds a list of other Nodes—and then the compiler will start screaming at you about ownership and recursive types.
Let's look at a piece of code I see learners write all the time. They realize they need multiple things to "own" a node (because in a graph, many nodes can point to one), so they reach for Rc. But then they realize they actually need to change the graph after it's created.
use std::rc::Rc;
struct Node {
value: String,
edges: Vec<Rc<Node>>,
}
fn main() {
let a = Rc::new(Node { value: "A".into(), edges: vec![] });
let b = Rc::new(Node { value: "B".into(), edges: vec![] });
// I want to connect A to B
a.edges.push(Rc::clone(&b)); // ERROR: cannot borrow `a` as mutable
}
The "Immutable Rc" Wall
If you try to compile that, Rust will stop you cold. Why? Because Rc<T> provides shared ownership, but it only provides immutable access to the data it wraps. Since multiple Rc pointers can point to the same Node, Rust cannot possibly know who "owns" the node enough to safely let them mutate it. If a could mutate the edges vector, it might invalidate pointers being held by other parts of the program.
You might be tempted to use mut a, but that only makes the pointer mutable, not the data inside the Rc. We need a way to cheat the system—legally.
Breaking Through with Interior Mutability
To fix this, we need to wrap the Node inside a RefCell. This is the "Interior Mutability" pattern. By using Rc<RefCell<Node>>, we move the borrow checking from compile-time to runtime. Instead of the compiler proving the code is safe, the RefCell will check at runtime if there are any active borrows and panic if you try to violate the rules (like having two mutable references at once).
Here is how we actually implement this. Notice how we have to call .borrow_mut() to get to the inner Node:
use std::rc::Rc;
use std::cell::RefCell;
struct Node {
value: String,
edges: Vec<Rc<RefCell<Node>>>,
}
fn main() {
let a = Rc::new(RefCell::new(Node {
value: "A".into(),
edges: vec![]
}));
let b = Rc::new(RefCell::new(Node {
value: "B".into(),
edges: vec![]
}));
// We borrow the interior of 'a' mutably to push 'b' into its edges
a.borrow_mut().edges.push(Rc::clone(&b));
// Now 'a' points to 'b'. We can do the same for 'b' to 'a' if we want a cyclic graph.
b.borrow_mut().edges.push(Rc::clone(&a));
println!("Graph connected successfully!");
}
I'll give you a heads-up: this pattern is powerful, but it comes with a cost. Because we're using Rc, we've introduced the possibility of reference cycles. In the example above, a points to b and b points to a. Their reference counts will never hit zero, meaning they'll stay in memory forever (a memory leak). In a real production system, you'd use Weak<T> to break those cycles, but for most graph exercises, Rc<RefCell<T>> is the essential foundation you need to master first.
📋 Practical Task
Exercise: Building a Social Connection Graph
Your task is to implement a basic social network graph where "Users" can be friends with each other. Because friendship is mutual, each user should hold a reference to their friends.
Requirements:
- Create a
Userstruct that contains aname: Stringand afriends: Vec<Rc<RefCell<User>>>. - Implement a function
add_friend(user1: &Rc<RefCell<User>>, user2: &Rc<RefCell<User>>). This function must adduser2touser1's friends list AND adduser1touser2's friends list. - In
main:- Create three users: "Alice", "Bob", and "Charlie".
- Make Alice and Bob friends.
- Make Bob and Charlie friends.
- Print out Alice's friends' names to verify the connection.
Hint: Remember that you cannot access the friends vector directly through the Rc; you must use .borrow_mut() to modify the list.
There are no comments for now.