Sling Academy
Home/Rust/Building Linked Lists and Trees with Rust Smart Pointers

Building Linked Lists and Trees with Rust Smart Pointers

Last updated: January 06, 2025

Rust is a systems programming language that's known for its safety and performance. A key feature of Rust that contributes to its abilities in these areas is its ownership and borrowing system. However, to manage multiple ownerships and more complex data structures like linked lists and trees, Rust provides what are called "smart pointers." In this article, we will explore how you can utilize Rust's smart pointers, specifically, Rc and RefCell, to construct linked lists and trees.

Understanding Smart Pointers

In Rust, a smart pointer is a data structure that not only acts as a pointer but also holds metadata and extra capabilities. The Box type is the simplest example, which ensures dynamic memory allocation. For our purposes, we'll focus on Rc and RefCell.

Basics of Rc and RefCell

The Rc stands for reference counting, which allows multiple ownership of data by tracking the number of references. It enables shared ownership of immutable data.

use std::rc::Rc;

let a = Rc::new(5);
let b = Rc::clone(&a); // Increase reference count

The RefCell provides interior mutability, which means you can mutate the data even when the RefCell itself is immutable. This is tracked at runtime to ensure that you borrow the data correctly as either mutable or immutable.

use std::cell::RefCell;

let value = RefCell::new(5);
*value.borrow_mut() = 10; // Mutate the value within RefCell

Constructing a Linked List

A simple linked list can be implemented using a combination of Rc and RefCell. The Rc allows nodes to share ownership, and RefCell allows interior mutability to modify nodes.

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

type Link = Option>>;

struct Node {
    value: i32,
    next: Link,
}

fn build_list(values: Vec) -> Link {
    let mut head: Link = None;
    for &value in values.iter().rev() {
        head = Some(Rc::new(RefCell::new(Node { value, next: head })));
    }
    head
}

In this snippet, build_list builds a linked list from a vector of integers. It reverses the vector and each time creates a new node that points to the current head.

Building a Tree

Trees can also be managed using Rc and RefCell by allowing nodes multiple children and shared ownership of their parents.

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

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

fn build_tree(value: i32, left: Option>>, right: Option>>) -> Rc> {
    Rc::new(RefCell::new(TreeNode { value, left, right }))
}

This simple structure represents a binary tree node that contains a numeric value with optional left and right subtree links.

Considerations and Safety

With smart pointers like Rc and RefCell, you gain flexibility in handling multiple ownership and mutable structures. However, this comes at the cost of runtime checks, and improper use can cause runtime errors such as borrowing panics. Always follow Rust's borrowing rules and consider other smart pointers like Arc for concurrent environments.

Conclusion

Rust’s smart pointers are powerful tools for constructing advanced data structures like linked lists and trees. By understanding Rc and RefCell, developers can effectively manage shared ownership and interior mutability, expanding the capabilities of Rust programs.

Next Article: Profiling and Debugging Reference Leaks in Rust Through Rc Cycles

Previous Article: Handling Complex Data Graphs in Rust: Rc, Arc, and Borrowing Strategies

Series: Closures and smart pointers in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior