Skip to Content
Course content

247: Common Rust Interview Questions on Smart Pointers

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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_parent method correctly assigns a weak reference.
  • You implement a get_parent_name method that attempts to upgrade the Weak pointer to an Rc to retrieve the parent's name, handling the None case gracefully.
  • The memory is successfully reclaimed when the root node is dropped (you can verify this by adding a println! inside a custom impl 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.
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.