Login Register
×

What Is an Rc Pointer In Rust?

In Rust, Rc<T> stands for a reference-counted smart pointer. It is a powerful feature that allows multiple parts of your program to share ownership of the same data stored on the heap.

With Box<T>, only one owner can exist at a time, but with Rc<T>, you can have multiple owners safely reading the same data without needing to copy it.

How Rc<T> Works In Rust?

When you wrap a value inside Rc<T>, Rust internally maintains a reference count, so a number representing how many Rc pointers are pointing to that same value.

  • Whenever you clone the Rc, the reference count increases.
  • Whenever one of the clones goes out of scope, the count decreases.
  • When the count reaches zero, Rust frees the memory automatically.

This system ensures that you never free memory too early (while it’s still in use) or forget to free it (avoiding memory leaks).

When We Need To Use Rc<T>?

  • You can use Rc<T> when you want to share ownership of data.
  • Use Rc<T> when multiple parts of your program need to read the same value.
  • You are working in a single-threaded environment (since Rc<T> is not thread-safe).

If you need shared ownership across multiple threads, you should use Arc<T> (Atomic Reference Counted), which works the same way but is designed for concurrent scenarios.

How to Create and Use an Rc Pointer

The Rc<T> allows shared ownership of data. We can use Rc::new() to create one, but if you want to share the same data with multiple owners, you can clone the Rc pointer.

Cloning an Rc does not copy the actual data; it just increases the reference count, which keeps track of how many owners exist.

Example: Creating and Cloning an Rc Pointer

use std::rc::Rc;

fn main() {
// Step 1: Create an Rc pointer for a string
let text = Rc::new(String::from("Hello, Rc Pointer!"));

// Step 2: Clone the Rc to create shared ownership
let shared1 = Rc::clone(&text); // Reference count increases
let shared2 = Rc::clone(&text); // Another shared reference

// Step 3: Use the Rc pointers
println!("text: {}, shared1: {}, shared2: {}", text, shared1, shared2);

// Step 4: Check how many references exist
println!("Reference count: {}", Rc::strong_count(&text));
}

Explanation of this code:

  • Rc::new() creates the first Rc pointer and stores the data (String) on the heap.
  • Rc::clone(&text) creates a new reference to the same heap data.

Use Cases for Rc<T> In Rust

1) Graph-Like Data Structures: Rc<T> allows multiple nodes to share ownership of the same node safely, without duplicating data or violating Rust’s ownership rules.

2) Shared Immutable Data: If you have read-only data that many parts of a program need access to, Rc<T> is perfect. Each part can clone the Rc pointer and read the data without copying it, ensuring efficient memory usage.

3) Tree Structures: Rc<T> allows multiple tree nodes to share ownership of children safely, keeping memory management automatic.

    Example: Using Rc in a Tree Structure

    use std::rc::Rc;

    // Define a tree node structure
    #[derive(Debug)]
    struct TreeNode {
    value: i32,
    next: Option<Rc<TreeNode>>,
    }

    fn main() {
    // Step 1: Create the first node
    let leaf = Rc::new(TreeNode { value: 1, next: None });

    // Step 2: Create a parent node that shares ownership of the leaf
    let root = Rc::new(TreeNode {
    value: 2,
    next: Some(Rc::clone(&leaf)),
    });

    // Step 3: Print both nodes
    println!("Leaf Node: {:?}", leaf);
    println!("Root Node: {:?}", root);

    // Step 4: Check reference count of the shared leaf
    println!("Reference Count of leaf node: {}", Rc::strong_count(&leaf));
    }

    Explanation:

    • The leaf is the first node of the tree, stored on the heap inside an Rc.
    • Rc::strong_count(&leaf) returns 2 because both leaf and root.next point to the same TreeNode.

    Important Methods in Rc<T>

    The Rc<T> smart pointer provides several useful methods to manage shared ownership and track references.

    1) Rc::new(value) – Create a New Rc Pointer

    • This method creates a new Rc pointer that owns the given value on the heap.
    use std::rc::Rc;

    fn main() {
    let number = Rc::new(100); // Create a heap-allocated Rc pointer
    println!("Value inside Rc: {}", number);
    }
    • This method allocate the value 100 on the heap.
    • The number becomes the first owner of this value.

    2) Rc::clone(&rc) – Clone the Reference

    • Cloning an Rc does not copy the actual data. Instead, it creates another owner pointing to the same heap-allocated value and increases the reference count.
    let original = Rc::new(String::from("Hello Rc"));
    let clone1 = Rc::clone(&original); // Reference count increases
    let clone2 = Rc::clone(&original); // Reference count increases again
    • All three (original, clone1, clone2) point to the same data.

    3) Rc::strong_count(&rc) – Number of Strong References

    • This method returns the number of active owners of the data.
    println!("Strong count: {}", Rc::strong_count(&original)); // Output: 3

    4) Rc::weak_count(&rc)

    This method returns the number of active owners of the data.

    println!("Strong count: {}", Rc::strong_count(&original)); // Output: 3
    • Helps you track how many Rc pointers currently own the value.
    • When the strong count reaches 0, Rust deallocates the memory.

    Limitations of Rc<T>

    There are some limitations of Rc<T> pointer:

    a) Single-Threaded Only: Rc<T> is designed for single-threaded programs. It is not thread-safe, so using it across multiple threads will result in a compile-time error.

    For multi-threaded applications, use Arc<T> (Atomic Reference Counted), which provides thread-safe shared ownership.

    b) Immutable Sharing: By default, Rc<T> only allows immutable access to its data. This is because multiple owners may exist, and allowing mutation could violate Rust’s safety rules, so you cannot directly mutate the data inside an Rc.

    Combine Rc<T> with RefCell<T> to allow interior mutability. For example:

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

    fn main() {
    let shared = Rc::new(RefCell::new(10));
    *shared.borrow_mut() += 5;
    println!("Updated value: {}", shared.borrow());
    }

      How To Combine Rc<T> with RefCell<T>?

      In Rust, Rc<T> allows shared ownership of data, but by default, it only permits immutable access.

      Example: Mutable Sharing with Rc and RefCell

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

      fn main() {
      // Step 1: Create shared data with Rc and RefCell
      let shared_value = Rc::new(RefCell::new(10));

      // Step 2: Clone the Rc pointer to create multiple owners
      let owner1 = Rc::clone(&shared_value);
      let owner2 = Rc::clone(&shared_value);

      // Step 3: Mutate the data through one owner
      *owner1.borrow_mut() += 5;

      // Step 4: Access the updated value from any owner
      println!("Shared Value: {}", shared_value.borrow());
      println!("Reference Count: {}", Rc::strong_count(&shared_value));
      }

      How To Prevent Memory Leaks with Weak<T>?

      A reference cycle happens when two or more Rc pointers point to each other. Because each pointer thinks the other is still using the memory, Rust cannot free it, even if nothing else in the program needs it.

      It doesn’t count as an owner, so it doesn’t increase the reference count. This allows you to break the cycle while still being able to access the data if it hasn’t been deleted yet.

      Example: Using Weak to Avoid Cycles

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

      // Define a tree node structure with parent and children
      #[derive(Debug)]
      struct Node {
      value: i32,
      parent: RefCell<Weak<Node>>, // Weak reference to parent
      children: RefCell<Vec<Rc<Node>>>, // Strong references to children
      }

      fn main() {
      // Step 1: Create the root node
      let root = Rc::new(Node {
      value: 1,
      parent: RefCell::new(Weak::new()), // No parent yet
      children: RefCell::new(vec![]),
      });

      // Step 2: Create a child node with a weak reference to the parent
      let child = Rc::new(Node {
      value: 2,
      parent: RefCell::new(Rc::downgrade(&root)), // Weak reference
      children: RefCell::new(vec![]),
      });

      // Step 3: Add the child to the root's children
      root.children.borrow_mut().push(Rc::clone(&child));

      // Step 4: Print the nodes
      println!("Root Node: {:?}", root);
      println!("Child Node: {:?}", child);
      }

      In this code:

      • Rc::downgrade(&root) converts a strong Rc reference into a weak reference (Weak<T>).
      • Children are strong Rc pointers because the parent owns the children, but the parent is a weak pointer because the child should not own the parent, avoiding cycles.

      Rc Pointer Exercise: Shared Bookmarks

      Create a program where multiple users share access to the same “bookmark” and track how many users are currently referencing it.

      Problem Statement

      1. Define a Bookmark struct with a title field.
      2. Wrap the Bookmark inside an Rc pointer.
      3. Create 3 users that each hold a clone of the Rc pointer to the same bookmark.
      4. Print the bookmark title from each user.
      5. Print the current reference count of the Rc pointer.
      6. Drop one of the users and print the updated reference count.

      All users should see the same bookmark title, and the reference count should increase when cloned and decrease when one user goes out of scope.

      Starter Code

      use std::rc::Rc;

      #[derive(Debug)]
      struct Bookmark {
      title: String,
      }

      fn main() {
      // Step 1: Create a bookmark wrapped in Rc
      let my_bookmark = Rc::new(Bookmark {
      title: String::from("Rust Programming Guide"),
      });

      // Step 2: Clone Rc for multiple users
      let user1 = Rc::clone(&my_bookmark);
      let user2 = Rc::clone(&my_bookmark);
      let user3 = Rc::clone(&my_bookmark);

      // Step 3: Print bookmark titles
      println!("User1 sees: {}", user1.title);
      println!("User2 sees: {}", user2.title);
      println!("User3 sees: {}", user3.title);

      // Step 4: Print reference count
      println!("Reference count: {}", Rc::strong_count(&my_bookmark));

      // Step 5: Drop user2 and check count
      drop(user2);
      println!("Reference count after dropping user2: {}", Rc::strong_count(&my_bookmark));
      }

      Learn Other Topics About Rust