When writing system-level code or complex data structures like trees and graphs in Rust, one challenge quickly becomes apparent:

how do you share and mutate data across many parts of a program without sacrificing memory safety?

Rust’s ownership system doesn’t allow multiple &mut references or shared ownership by default.

But the standard library offers powerful tools to overcome this — while maintaining safety guarantees.

In this post, we explore some of Rust’s most important tools for working with shared, recursive, and mutable data structures:

  • Box
  • Rc / Arc
  • RefCell / Mutex
  • Weak

We use a tree structure as our running example and discuss real-world system programming, GUIs, and interpreters.


📆 Box: Owning Recursive Types

struct TreeNode {
    value: i32,
    left: Option<Box<TreeNode>>,
    right: Option<Box<TreeNode>>,
}

🔍 Why Use Box?

  • Enables recursive types by storing nodes on the heap
  • Only allows single ownership (no sharing)

✅ When to Use:

  • Heap allocation for simple trees, linked lists
  • No need for shared or mutable access
  • Example: Binary tree leaves in a compiler, simple DOM nodes in a web renderer

♻️ Rc and RefCell: Shared & Mutable Trees

use std::rc::Rc;
use std::cell::RefCell;

type NodeRef = Rc<RefCell<Node>>;

struct Node {
    value: i32,
    children: Vec<NodeRef>,
}

impl Node {
    fn new(value: i32) -> NodeRef {
        Rc::new(RefCell::new(Node { value, children: vec![] }))
    }
}

🔍 Why Combine Rc + RefCell?

  • Rc enables shared ownership
  • RefCell enables interior mutability, allowing mutation even through &self

✅ When to Use:

  • Build tree or graph structures
  • Allow many owners to read and mutate data
  • Example: GUI widget trees (druid, iced), ASTs for interpreters

⚠️ Memory Leaks Warning

Rc uses reference counting — but cannot detect cycles!

If two Rcs reference each other, they’ll leak memory.


🧰 Weak: Breaking Cycles Safely

use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Node {
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

🔍 Why Use Weak?

  • Weak is a non-owning reference
  • Does not increase the strong reference count
  • Prevents cyclic leaks between parent and child nodes

✅ When to Use:

  • Parent pointers in trees
  • Bidirectional linked structures
  • Example: Filesystem directory trees, scene graphs

🔐 Mutex and Arc: Thread-Safe Shared State

When moving to multithreading, Rc and RefCell aren't enough.

Instead, use:

use std::sync::{Arc, Mutex};

let data = Arc::new(Mutex::new(vec![1, 2, 3]));

let cloned = data.clone();
std::thread::spawn(move || {
    let mut locked = cloned.lock().unwrap();
    locked.push(4);
});

🔍 Why Use Arc + Mutex?

  • Arc = atomic reference counted smart pointer (safe across threads)
  • Mutex = exclusive mutable access with runtime locking

✅ When to Use:

  • Share state between threads
  • Build concurrent task queues, database caches
  • Example: Shared blockchain ledger across network threads

✅ Summary: Choose the Right Tool

Tool Ownership Mutability Thread-Safe? Example
Box Single No Recursive tree node
Rc Shared (single-threaded) No AST nodes
RefCell Interior Yes (runtime check) Cache inside Rc
Arc Shared (multi-threaded) No Multithreaded P2P socket pool
Mutex Exclusive Yes (lock) Task queue, ledger state
Weak Non-owning Read-only unless upgraded Break cycles (parent-child trees)

🧐 Final Thoughts

Understanding Box, Rc, RefCell, Arc, Mutex, and Weak is essential if you want to write serious system-level Rust.

These tools aren't "cheating" Rust’s rules — they are specialized, powerful, safe ways to express complicated relationships (ownership, sharing, mutation, concurrency) while keeping the Rust safety guarantees.

With these patterns, you can build:

  • Blockchain nodes
  • Compilers
  • Embedded device controllers
  • Multithreaded servers
  • Real-time operating systems (RTOS)

Master these — and you master Rust system programming.