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
- Define a Bookmark struct with a title field.
- Wrap the Bookmark inside an Rc pointer.
- Create 3 users that each hold a clone of the Rc pointer to the same bookmark.
- Print the bookmark title from each user.
- Print the current reference count of the Rc pointer.
- 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
- What are threads in Rust?
- What is concurrency in Rust?
- What is error handling in Rust?
- What is an HashMap in Rust?
- What is an Iterator in Rust?
- What is Mutex in Rust?
- What is Async in Rust?

M.Sc. (Information Technology). I explain AI, AGI, Programming and future technologies in simple language. Founder of BoxOfLearn.com.