← Back to Rust Course | Chapter 13: Smart Pointers & Unsafe | Lesson 2 of 7

Rc and Arc

Rc and Arc let more than one owner share the same value, counting how many are still using it before cleaning up.
Syntax
rust
let a = Rc::new(value);
let b = Rc::clone(&a);
Rc::strong_count(&a);

let a = Arc::new(value);  // thread-safe

Sharing Data with Rc

Rc::new(value) wraps a value for shared ownership; calling .clone() on it produces another owner pointing to the same data and increments an internal reference count.

Example: Sharing Data with Rc

markup
// Bring `std::rc::Rc` into scope
use std::rc::Rc;

// Entry point: execution of the program starts here
fn main() {
    // Declare an immutable variable `shared`, set to `Rc::new(String::from("shared data"))`
    let shared = Rc::new(String::from("shared data"));
    // Declare an immutable variable `owner_two`, set to `Rc::clone(&shared)`
    let owner_two = Rc::clone(&shared);
    // Print "{} and {}" to the console, substituting in `shared, owner_two` (with a trailing newline)
    println!("{} and {}", shared, owner_two);
    // Print "Reference count: {}" to the console, substituting in `Rc::strong_count(&shared)` (with a trailing newline)
    println!("Reference count: {}", Rc::strong_count(&shared));
}

Rc Clone Is Cheap

Cloning an Rc does not deep-copy the underlying data -- it only increments the reference count, making it a very cheap operation regardless of how large the shared data is.

Example: Rc Clone Is Cheap

markup
// Bring `std::rc::Rc` into scope
use std::rc::Rc;

// Entry point: execution of the program starts here
fn main() {
    // Declare an immutable variable `data`, set to `Rc::new(vec![1, 2, 3, 4, 5])`
    let data = Rc::new(vec![1, 2, 3, 4, 5]);
    // Declare an immutable variable `clone_a`, set to `Rc::clone(&data)`
    let clone_a = Rc::clone(&data);
    // Declare an immutable variable `clone_b`, set to `Rc::clone(&data)`
    let clone_b = Rc::clone(&data);
    // Print "Count: {}" to the console, substituting in `Rc::strong_count(&data)` (with a trailing newline)
    println!("Count: {}", Rc::strong_count(&data));
    // Print "{:?}" to the console, substituting in `clone_a` (with a trailing newline)
    println!("{:?}", clone_a);
    // Print "{:?}" to the console, substituting in `clone_b` (with a trailing newline)
    println!("{:?}", clone_b);
}

Using Arc for Thread Safety

Arc<T> behaves like Rc<T> but uses atomic operations for its reference count, making it safe to share across multiple threads, unlike plain Rc.

Example: Using Arc for Thread Safety

markup
// Bring `std::sync::Arc` into scope
use std::sync::Arc;
// Bring `std::thread` into scope
use std::thread;

// Entry point: execution of the program starts here
fn main() {
    // Declare an immutable variable `shared`, set to `Arc::new(String::from("shared across threads"))`
    let shared = Arc::new(String::from("shared across threads"));
    // Declare an immutable variable `shared_clone`, set to `Arc::clone(&shared)`
    let shared_clone = Arc::clone(&shared);
    let handle = thread::spawn(move || {
        // Print "From thread: {}" to the console, substituting in `shared_clone` (with a trailing newline)
        println!("From thread: {}", shared_clone);
    });
    handle.join().unwrap();
    // Print "From main: {}" to the console, substituting in `shared` (with a trailing newline)
    println!("From main: {}", shared);
}

Value Dropped When Count Reaches Zero

The underlying data wrapped by Rc/Arc is only actually freed once every owner has been dropped and the reference count falls to zero, which happens automatically without manual bookkeeping.

Example: Value Dropped When Count Reaches Zero

markup
// Bring `std::rc::Rc` into scope
use std::rc::Rc;

// Entry point: execution of the program starts here
fn main() {
    // Declare an immutable variable `a`, set to `Rc::new(10)`
    let a = Rc::new(10);
    // Print "Count after creating a: {}" to the console, substituting in `Rc::strong_count(&a)` (with a trailing newline)
    println!("Count after creating a: {}", Rc::strong_count(&a));
    {
        // Declare an immutable variable `_b`, set to `Rc::clone(&a)`
        let _b = Rc::clone(&a);
        // Print "Count with b alive: {}" to the console, substituting in `Rc::strong_count(&a)` (with a trailing newline)
        println!("Count with b alive: {}", Rc::strong_count(&a));
    }
    // Print "Count after b dropped: {}" to the console, substituting in `Rc::strong_count(&a)` (with a trailing newline)
    println!("Count after b dropped: {}", Rc::strong_count(&a));
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Trying to mutate data inside a plain Rc directly -- Rc only allows shared, immutable access on its own.
  2. Using Rc across multiple threads, when Arc (atomic reference counting) is required for thread safety.
  3. Creating a reference cycle with Rc (two values owning each other), which leaks memory since the count never reaches zero.
Chapter Summary
  • Rc<T> (Reference Counted) enables multiple owners of the same heap-allocated value within a single thread.
  • Arc<T> (Atomically Reference Counted) is the thread-safe equivalent of Rc, usable across multiple threads.
  • .clone() on an Rc/Arc increases the reference count rather than deep-copying the underlying data.
  • The value is only actually dropped once its reference count reaches zero.
🔒

Chapter Quiz — Complete all 7 topics to unlock

0/7 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.