Rust Lesson 23 Rc and Arc – TITLE HERE | Dataplexa

Box, Rc & Arc in Rust

In this lesson, you will learn about three important Rust smart pointers: Box, Rc, and Arc. These types help you manage heap memory and ownership in different scenarios.

Rust is strict about ownership, so these smart pointers provide safe ways to handle:

  • Heap allocation (Box<T>)
  • Shared ownership in single-threaded programs (Rc<T>)
  • Shared ownership in multi-threaded programs (Arc<T>)

1) Box<T> — Single Ownership on the Heap

Box<T> stores a value on the heap and gives it a single owner. This is useful when:

  • You want to store large data on the heap
  • You need a type with a known size (useful for recursive types)
  • You want stable memory allocation
let num = Box::new(500);
println!("{}", num);

When the box goes out of scope, Rust automatically frees the memory.


Box Example: Recursive Type (Why Box Exists)

Rust needs types to have a known size at compile time. Recursive structures like linked lists require Box.

enum List {
    Cons(i32, Box),
    Nil,
}

let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));

2) Rc<T> — Shared Ownership (Single-Threaded)

Rc<T> stands for Reference Counted. It allows multiple owners of the same data in a single-threaded program.

When you clone an Rc, it does NOT copy the inner data. It only increases the reference count.

use std::rc::Rc;

let a = Rc::new(String::from("Dataplexa"));
let b = Rc::clone(&a);
let c = Rc::clone(&a);

println!("a count = {}", Rc::strong_count(&a));
println!("b = {}", b);
println!("c = {}", c);

The data is freed only when the reference count becomes 0.


When Should You Use Rc?

Use Rc<T> when:

  • You need shared ownership
  • Your program is single-threaded
  • You want automatic cleanup when the last owner is gone

3) Arc<T> — Shared Ownership (Multi-Threaded)

Arc<T> stands for Atomic Reference Counted. It is like Rc but safe for multi-threading.

It uses atomic operations to track the reference count, which makes it thread-safe but slightly slower than Rc.

use std::sync::Arc;
use std::thread;

let data = Arc::new(String::from("Rust is fast"));

let t1 = {
    let d = Arc::clone(&data);
    thread::spawn(move || {
        println!("Thread 1: {}", d);
    })
};

let t2 = {
    let d = Arc::clone(&data);
    thread::spawn(move || {
        println!("Thread 2: {}", d);
    })
};

t1.join().unwrap();
t2.join().unwrap();

Rc vs Arc (Important Difference)

The key difference is thread safety:

  • Rc<T> is NOT thread-safe (single-thread only)
  • Arc<T> IS thread-safe (works across threads)

If you try using Rc inside threads, Rust will throw a compile-time error.


Quick Summary Table

This summary helps you pick the right tool:

  • Box: One owner, heap storage, simplest smart pointer
  • Rc: Multiple owners, single-threaded
  • Arc: Multiple owners, multi-threaded

📝 Practice Exercises


Exercise 1

Create a Box<i32> and print the value.

Exercise 2

Create an Rc<String>, clone it twice, and print the reference count.

Exercise 3

Create an Arc<String> and print it from two threads.

Exercise 4

Write one sentence explaining when to use Box vs Rc vs Arc.


✅ Practice Answers


Answer 1

let x = Box::new(99);
println!("{}", x);

Answer 2

use std::rc::Rc;

let a = Rc::new(String::from("hello"));
let _b = Rc::clone(&a);
let _c = Rc::clone(&a);

println!("count = {}", Rc::strong_count(&a));

Answer 3

use std::sync::Arc;
use std::thread;

let data = Arc::new(String::from("shared"));

let t1 = {
    let d = Arc::clone(&data);
    thread::spawn(move || println!("T1: {}", d))
};

let t2 = {
    let d = Arc::clone(&data);
    thread::spawn(move || println!("T2: {}", d))
};

t1.join().unwrap();
t2.join().unwrap();

Answer 4

Use Box for single ownership on the heap, Rc for shared ownership in one thread, and Arc for shared ownership across multiple threads.


What’s Next?

In the next lesson, you will learn about Option and Result types and how Rust uses them for safe programming without null values.