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
247: Common Rust Interview Questions on Smart Pointers
I remember mentoring a developer a few years back who was building a dependency graph for a build tool. He had spent three days fighting the borrow checker, trying to force a Box to handle multiple parents for a single node. When he finally got it working using Rc<RefCell<T>>, he felt like he'd hacked his way to victory. However, during the subsequent architectural review, the lead engineer asked him a single question: "Why did you choose Rc over Arc here, and what happens to the memory if two nodes point to each other?" He froze. He knew the code worked, but he couldn't explain the why. In a high-stakes interview, that silence is usually where the conversation ends.
Choosing the Right Tool for the Job
When an interviewer asks you to compare Box, Rc, and Arc, they aren't looking for a dictionary definition. They want to see if you understand the cost of memory management. Box<T> is your simplest tool; it's just a pointer to the heap. Use it when you have a recursive type or when you need to move a large amount of data without copying it on the stack. It's unique ownership, period.
Things get interesting when you need shared ownership. Rc<T> (Reference Counted) allows multiple owners, but it's strictly for single-threaded scenarios. If you try to send an Rc across a thread boundary, the compiler will stop you because the internal reference counter isn't atomic. That's where Arc<T> (Atomic Reference Counted) comes in. It does the exact same thing as Rc, but it uses atomic operations to update the count, making it thread-safe. The trade-off? Atomic operations are more expensive than regular ones. If you use Arc in a single-threaded loop, you're paying a performance tax for a safety feature you aren't even using.
// Quick cheat sheet for your mental model:
// Box -> Single owner, heap allocation.
// Rc -> Multiple owners, single-threaded.
// Arc -> Multiple owners, multi-threaded.
The Magic of Interior Mutability
A common "gotcha" question involves the intersection of shared ownership and mutability. You'll likely be asked: "If I have an Rc<T>, how do I change the data inside it?" The answer is RefCell<T> or Mutex<T>. This is what we call "interior mutability."
Normally, Rust enforces borrowing rules at compile time. But RefCell<T> moves those checks to runtime. It allows you to mutate data even when you only have an immutable reference to the RefCell itself. You use .borrow_mut() to get a mutable reference. If you accidentally try to borrow it mutably twice at the same time, your program will panic. I always tell my students to think of RefCell as a "trust me, I know what I'm doing" button for the compiler. In a multi-threaded context, you'd swap RefCell for a Mutex or RwLock to ensure that only one thread can mutate the data at a time.
Avoiding the Reference Counting Trap
If you're interviewing for a senior role, expect a question about memory leaks in Rust. Most people think Rust prevents all leaks, but that's not true. The classic example is the reference cycle. If Node A has an Rc to Node B, and Node B has an Rc to Node A, their reference counts will never drop to zero. They'll sit in your heap forever, long after the rest of your program has forgotten about them.
The solution is Weak<T>. A Weak pointer is like an Rc, but it doesn't contribute to the ownership count. It's a "non-owning" reference. To use the data inside a Weak pointer, you have to "upgrade" it back to an Rc, which returns an Option. This forces you to handle the case where the data might have already been dropped. When designing a parent-child relationship in a tree or graph, the gold standard is: parents own children via Rc, and children point back to parents via Weak.
📋 Practical Task
Breaking the Cycle: Implementing Weak Pointers in a Parent-Child Node Tree
You are given a basic implementation of a Node structure where parents and children both hold Rc pointers to each other. This is causing a memory leak because the reference counts never reach zero, preventing the drop method from ever being called.
Your Task: Modify the Node struct and its implementation to use Weak<RefCell<Node>> for the parent reference instead of Rc<RefCell<Node>>. You must ensure that:
- The
set_parentmethod correctly assigns a weak reference. - You implement a
get_parent_namemethod that attempts to upgrade theWeakpointer to anRcto retrieve the parent's name, handling theNonecase gracefully. - The memory is successfully reclaimed when the root node is dropped (you can verify this by adding a
println!inside a customimpl Drop for Node).
use std::rc::{Rc, Weak};
use std::cell::RefCell;
struct Node {
name: String,
parent: RefCell<Option<Weak<RefCell<Node>>>>, // Change this from Rc to Weak
children: RefCell<Vec<Rc<RefCell<Node>>>>,
}
impl Node {
fn new(name: &str) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Node {
name: name.to_string(),
parent: RefCell::new(None),
children: RefCell::new(vec![]),
}))
}
fn add_child(parent: &Rc<RefCell<Node>>, child: &Rc<RefCell<Node>>) {
// TODO: Implement the logic to add the child to the parent's children list
// AND set the child's parent to be a Weak reference to the parent.
}
}
impl Drop for Node {
fn drop(&mut self) {
println!("Dropping node: {}", self.name);
}
}
fn main() {
let root = Node::new("Root");
let child = Node::new("Child");
Node::add_child(&root, &child);
println!("Parent-child link established.");
// When root and child go out of scope here, they should both be dropped.
}
There are no comments for now.