Rc<T>: Reference-Counted Shared Ownership

In most cases, ownership is clear: you know exactly which variable owns a given value. However, sometimes a single value needs to be owned by multiple parts of your program. For example, in a graph data structure, multiple edges might point to the same node, and logically that node is owned by all the edges that point to it.

For these cases, Oxide provides Rc<T>, a type that enables reference counting. Rc stands for "Reference Counted." An Rc<T> keeps track of how many owners a value has and only frees the value when there are no more owners.

When to Use Rc<T>

  • When you have data that needs to be owned by multiple parts of your program
  • When you don't know at compile time which part will finish using the data last
  • In graph structures where multiple nodes reference the same data
  • In single-threaded code (for multi-threaded, use Arc<T>)

A Practical Example: Graph with Multiple Owners

Let's say we're building a graph where multiple nodes can reference the same data:

public struct Node {
    public name: String,
    public neighbors: Vec<Rc<Node>>,
}

fn main() {
    // Create a node using Rc
    let node1 = Rc { Node {
        name: "Node 1",
        neighbors: vec![],
    } }

    // Clone the Rc to create another reference (not a copy of the data)
    let node2 = Rc.clone(&node1)

    // Both node1 and node2 point to the same data
    println!("Node 1: \(node1.name)")
    println!("Node 2: \(node2.name)")

    // The reference count is 2 at this point
    println!("Reference count: \(Rc.strongCount(&node1))")
}

Cloning an Rc<T>

When you call Rc.clone(&rcValue), you're creating another reference to the same value, not copying the value itself. This is different from calling clone() on the value inside.

let original = Rc { String.from("hello") }

// This creates another Rc pointing to the same String
let cloned = Rc.clone(&original)

// Both original and cloned point to the same String
println!("Reference count: \(Rc.strongCount(&original))")  // Prints 2

Why the explicit Rc.clone() instead of just .clone()? Because Rc.clone() is cheap—it just increments a counter—while clone() on the String inside would copy the entire string. Using Rc.clone() makes it explicit that you're doing cheap reference counting, not expensive data copying.

Reference Counting in Action

Let's trace through what happens with reference counts:

fn main() {
    // rc1 points to a String, reference count = 1
    let rc1 = Rc { String.from("hello") }
    println!("Count after creating rc1: \(Rc.strongCount(&rc1))")  // 1

    {
        // rc2 is a new reference to the same String, count = 2
        let rc2 = Rc.clone(&rc1)
        println!("Count after creating rc2: \(Rc.strongCount(&rc1))")  // 2

        // Inside this scope, both rc1 and rc2 are valid
        println!("rc1: \(rc1), rc2: \(rc2)")
    }  // rc2 goes out of scope, count decrements to 1

    println!("Count after rc2 goes out of scope: \(Rc.strongCount(&rc1))")  // 1
}  // rc1 goes out of scope, count = 0, String is dropped

Rc<T> with Structs

Here's a more practical example using structs:

public struct Cons<T> {
    public head: T,
    public tail: Rc<Cons<T>>?,
}

fn main() {
    // Create a list: [3, [5, [10]]]
    let list1 = Rc { Cons {
        head: 3,
        tail: Rc { Cons {
            head: 5,
            tail: Rc { Cons {
                head: 10,
                tail: null,
            } },
        } },
    } }

    // Create list2 by sharing the tail of list1
    let list2 = Rc { Cons {
        head: 20,
        tail: Rc.clone(&list1),
    } }

    // Create list3 by sharing the tail of list1
    let list3 = Rc { Cons {
        head: 30,
        tail: Rc.clone(&list1),
    } }

    // Now list1, list2, and list3 share the same tail data
    println!("list1 reference count: \(Rc.strongCount(&list1))")  // 3
}

Rc<T> Does NOT Enable Mutation

An important limitation of Rc<T> is that it gives you immutable references to the data inside. You cannot mutate data inside an Rc<T>:

let rc = Rc { vec![1, 2, 3] }
// rc.push(4)  // Error: cannot mutate through an Rc

This is by design. Since multiple parts of your code might be referencing the same data, allowing mutation could cause data races and undefined behavior.

If you need interior mutability alongside reference counting, combine Rc<T> with RefCell<T>, which we'll explore in the next section.

Rc<T> vs Other Ownership Models

OwnershipCostWhen to Use
Owned valueNoneSingle owner, known at compile time
&T referenceNoneTemporary borrowing
Box<T>MinimalSingle owner on heap
Rc<T>Reference count overheadMultiple owners (single-threaded)
Arc<T>Atomic reference countMultiple owners (multi-threaded)

Reference Counting Performance

Rc<T> has a small but real performance cost:

  1. Memory overhead: Each Rc allocates extra space for the reference count
  2. CPU overhead: Cloning increments a counter; dropping decrements it (atomic operations in Arc<T>)
  3. Pointer indirection: Accessing the data requires dereferencing the pointer

For most applications, this overhead is negligible. However, in performance-critical code or when creating millions of references, consider whether you really need Rc<T> or if another approach would be better.

Rc<T> is Single-Threaded

Rc<T> is designed for single-threaded programs. If you need reference counting in a multi-threaded program, use Arc<T> (Atomic Reference Counted) instead, which uses atomic operations for thread safety.

// Single-threaded (use Rc)
let rc = Rc { data }

// Multi-threaded (use Arc)
let arc = Arc { data }

Real-World Example: Document Structure

Here's a practical example of using Rc<T> for a document structure where paragraphs might share common styling:

public struct Paragraph {
    public text: String,
    public style: Rc<Style>,
}

public struct Style {
    public fontSize: Int,
    public fontColor: String,
}

fn main() {
    // Create a style that will be shared
    let headingStyle = Rc { Style {
        fontSize: 24,
        fontColor: "blue",
    } }

    let heading = Paragraph {
        text: "Welcome",
        style: Rc.clone(&headingStyle),
    }

    let subheading = Paragraph {
        text: "Introduction",
        style: Rc.clone(&headingStyle),
    }

    // Both paragraphs share the same style object
    println!("Style reference count: \(Rc.strongCount(&headingStyle))")  // 3
}

Summary

Rc<T> enables multiple ownership of the same value through reference counting:

  • Use Rc<T> when you need multiple parts of your program to own the same data
  • Clone an Rc<T> with Rc.clone() (cheap—increments a counter)
  • The data is only dropped when the reference count reaches zero
  • Rc<T> provides immutable access; combine with RefCell<T> for interior mutability
  • Use Arc<T> for multi-threaded code

In the next section, we'll see how to achieve interior mutability—the ability to mutate data through an immutable reference—using RefCell<T>.