---
title: 'Reference Counting'
source: 'https://academia.sh/en/courses/ownership/reference-counting'
course: 'Ownership and the Memory Model'
language: en
updated: '2026-09-15T04:58:41+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Runs code showing that a shared value's counter rises with every copy and falls with every end"
    - 'Confirms that the counter reaching zero coincides with the exact moment the value reaches the ledger'
    - 'Distinguishes with an example that taking a reference does not change the counter, only cloning does'
---

# Reference Counting

How an Rc value's counter rises with every clone and falls with every end, and how the value reaches the ledger only once the counter hits zero, is measured; that taking a reference never changes this counter, and that what decides the end is not which copy is dropped but how many remain, is shown.

The previous lesson measured that a value on the heap always had exactly one owner —
whether it moved, was dropped early, or was chained. This lesson's question lifts that very
assumption: can a value have more than one owner? The answer is yes — but this yes is not an
evasion, it is a deterministic form of sharing tracked by a counter readable from the
source. What the counter tracks is a value's end point — not how a running unit stops the
way the Go curriculum's Concurrency course measures.

`Rc<T>` is a container that places a value on the heap and counts how many bindings share
access to it. The value lives until this counter drops to zero; the instant the counter
hits zero, the single ownership seen in the previous lesson ends and the value reaches the
ledger. This lesson measures that counter directly, reading what happens on every clone and
every end, and which binding counts as an "owner" and which does not.

## Why a Reference Is Not Enough

The Borrowing lesson measured that a reference cannot outlive the value it borrows; the
Lifetime Concept lesson named this limit a validity range. This limit falls short for a use
that wants to reach the same value from several places but does not want to stay bound to
the lifetime of the single owning binding in the source: a reference becomes invalid once
its owner's scope closes, no matter how many places read it — the owner itself is still
singular. `Rc` gets past this limit by making a copy itself an owner: what is now involved
is not a borrowing party but a copy that is an owner in its own right, and that copy can
live until the end of its own scope independent of the original binding.

## Ownership Becomes Countable

When a value is placed on the heap with `Rc::new`, the counter is set to one. When
`Rc::clone` is called, a new copy is produced and the counter rises by one; when one of the
copies ends, the counter falls by one. These copies access the **same** value as one
another — none of them carries its own separate value, all of them share a single `Trace`.

**SO5.** — the claim that "the counter rises with every copy and falls with every end" is
confirmed by the counter values printed in the sequence `1, 2, 3, 2` in the run below.

```rust
// reference_counter.rs — Rc's counter rises with every copy and falls with every end
use std::cell::RefCell;
use std::rc::Rc;

type Ledger = Rc<RefCell<Vec<String>>>;

struct Trace {
    name: String,
    ledger: Ledger,
}

impl Trace {
    fn new(name: &str, ledger: &Ledger) -> Trace {
        Trace {
            name: name.to_string(),
            ledger: Rc::clone(ledger),
        }
    }
}

impl Drop for Trace {
    fn drop(&mut self) {
        self.ledger.borrow_mut().push(self.name.clone());
    }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(Vec::new()));
    {
        let shared: Rc<Trace> = Rc::new(Trace::new("shared", &ledger));
        println!("counter once built: {}", Rc::strong_count(&shared));

        let second = Rc::clone(&shared);
        println!("counter after one copy: {}", Rc::strong_count(&shared));

        {
            let _third = Rc::clone(&shared);
            println!("counter after two copies: {}", Rc::strong_count(&shared));
            println!("ledger length as the inner scope ends: {}", ledger.borrow().len());
        }
        println!("counter after the inner scope ends: {}", Rc::strong_count(&shared));
        println!("second copy still readable: {}", second.name);
    }
    let record = ledger.borrow();
    println!("record count in the ledger: {}", record.len());
    println!("records: {}", record.join(", "));
}
```

```
counter once built: 1
counter after one copy: 2
counter after two copies: 3
ledger length as the inner scope ends: 0
counter after the inner scope ends: 2
second copy still readable: shared
record count in the ledger: 1
records: shared
```

As the inner scope closes, `_third` ends, but the ledger length stays at zero: `_third`'s end
only drops the counter to two, it does not end the `Trace` value itself. `shared` and
`second` can still access the same value — reading `second.name` still gives the text
`shared`, because the two share the same value. When the outer scope closes, the remaining
two copies end as well, the counter drops to zero, and only at that instant does a single
record appear in the ledger. A single record in the ledger despite three copies shows that
sharing does not **multiply** the value, it only multiplies the number of paths that reach
it.

## Cloning Is Not Copying Data

In the run above, `Trace::new` is called only **once** — while `shared` is built. `second`
and `_third` are produced with `Rc::clone(&shared)`; this call neither reruns `Trace::new`
nor rebuilds the `name` field. That `second.name` still gives the text `shared` is not a
coincidence: `second` does not carry a separate `Trace` value, it carries a second access
path looking at the **same** value as `shared`. This differs from a `.clone()` call on a
string or a list — there the content is genuinely copied, and changing one afterward leaves
the other unchanged. `Rc::clone`, despite the verb in its name, multiplies not the value it
carries but only the counter and the access to it. This difference produces important
consequences later, when a lesson takes up **modifying** shared data: a change made through
one of the two copies becomes visible through the other one too, because in fact only one
value is being changed.

## Each Value Has Its Own Counter

If a program has several shared values, each has its own separate counter. A value being
cloned or ending never touches another value's counter.

```rust
// independent_counters.rs — different values' counters are independent of one another
use std::cell::RefCell;
use std::rc::Rc;

type Ledger = Rc<RefCell<Vec<String>>>;

struct Trace {
    name: String,
    ledger: Ledger,
}

impl Trace {
    fn new(name: &str, ledger: &Ledger) -> Trace {
        Trace {
            name: name.to_string(),
            ledger: Rc::clone(ledger),
        }
    }
}

impl Drop for Trace {
    fn drop(&mut self) {
        self.ledger.borrow_mut().push(self.name.clone());
    }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(Vec::new()));
    let first: Rc<Trace> = Rc::new(Trace::new("first-value", &ledger));
    let second: Rc<Trace> = Rc::new(Trace::new("second-value", &ledger));

    let first_copy = Rc::clone(&first);
    println!("first counter: {}", Rc::strong_count(&first));
    println!("second counter: {}", Rc::strong_count(&second));

    drop(first_copy);
    drop(first);
    println!("ledger (only first has ended): {}", ledger.borrow().join(", "));

    drop(second);
    println!("ledger (both have ended): {}", ledger.borrow().join(", "));
}
```

```
first counter: 2
second counter: 1
ledger (only first has ended): first-value
ledger (both have ended): first-value, second-value
```

`first`'s counter is two because it was cloned once, `second`'s counter is one because it
was never cloned. When both of `first`'s copies end, `second`'s counter still stays at
one — `second` is unaware of that change. The same `ledger` is shared between both values (it
is a recording tool, not the counted value itself), but each `Trace`'s own `Rc` wrapper
keeps its own counter. A program has as many separate, mutually independent counters as it
has separate shared values.

## Sharing Does Not Violate the Ownership Rule, It Generalizes It

The "single owner" rule built in the previous lesson is not broken here, it is generalized:
the owner is no longer a single binding, it is the **set** of copies that stay active until
the counter drops to zero. The instant the counter hits zero is precisely determined, and
that instant is a record always produced in the same order in the run — not a prediction
depending on what the next step does, but a direct result of the number of copies in the
source. This behaves like an ordinary integer running on a single thread: an `Rc::clone`
call produces a copy and raises the counter, a copy's end lowers it, and both operations run
from the same place — the same scope.

The counter is not limited to individually named bindings; copies produced in a loop follow
the same rule too. The run below collects five copies into a list, then empties the list in
one go.

```rust
// bulk_cloning.rs — copies produced by a loop return to the starting counter once cleared
use std::rc::Rc;

fn main() {
    let shared: Rc<i32> = Rc::new(7);
    let mut copies: Vec<Rc<i32>> = Vec::new();
    for _ in 0..5 {
        copies.push(Rc::clone(&shared));
    }
    println!("counter after five copies: {}", Rc::strong_count(&shared));

    copies.clear();
    println!("counter once copies are cleared: {}", Rc::strong_count(&shared));
}
```

```
counter after five copies: 6
counter once copies are cleared: 1
```

Once five copies are added to the list, the counter rises to six — the originally built
`shared` counts as a copy too. When the list is emptied in one go, the five copies end one
after another and the counter returns to one; because `shared` itself is still held, the
value has not ended yet. This example does not use the `Trace` rig, because what is measured
here is not an end record but that the counter itself follows the same rule under batch
operations too.

## What Is Counted Is an Owner, Not a Borrow

The counter only counts `Rc::clone` calls. Taking a **reference** to a shared value —
borrowing it to read it — never changes the counter, because a borrowing party is not the
value's owner, it only gains temporary access to it.

**SO6.** — the claim that "taking a reference does not change the counter" is confirmed by
the `counter once built` and `counter after borrowing and reading` lines in the run below
both giving the same value (`1`).

```rust
// borrow_skips_counter.rs — taking a reference never changes the Rc counter
use std::cell::RefCell;
use std::rc::Rc;

type Ledger = Rc<RefCell<Vec<String>>>;

struct Trace {
    name: String,
    ledger: Ledger,
}

impl Trace {
    fn new(name: &str, ledger: &Ledger) -> Trace {
        Trace {
            name: name.to_string(),
            ledger: Rc::clone(ledger),
        }
    }
}

impl Drop for Trace {
    fn drop(&mut self) {
        self.ledger.borrow_mut().push(self.name.clone());
    }
}

fn read(value: &Trace) -> usize {
    value.name.len()
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(Vec::new()));
    let shared: Rc<Trace> = Rc::new(Trace::new("sample-value", &ledger));
    println!("counter once built: {}", Rc::strong_count(&shared));

    let length = read(&shared);
    println!(
        "counter after borrowing and reading: {} (length {})",
        Rc::strong_count(&shared),
        length
    );

    let copy = Rc::clone(&shared);
    println!("counter after cloning: {}", Rc::strong_count(&shared));

    drop(copy);
    println!("counter once the copy is dropped: {}", Rc::strong_count(&shared));
}
```

```
counter once built: 1
counter after borrowing and reading: 1 (length 12)
counter after cloning: 2
counter once the copy is dropped: 1
```

The `read` function takes `&Trace`, not `Rc<Trace>` itself — so the call never affects the
counter. The counter only rises to two with an `Rc::clone` call and returns to one once the
copy is explicitly dropped. The principle established in the Borrowing lesson — that a
borrowed value does not end — holds here too: a reference never enters the value's ownership
ledger; only copies count as owners, borrowers do not.

This distinction looks small but is open to misunderstanding, because passing `&shared` to a
function through `Rc<Trace>` and passing `Rc::clone(&shared)` look superficially similar —
both can carry a `&` sign. The difference is this: the call `read(&shared)` borrows the
`Rc<Trace>` and automatically coerces down to the `&Trace` inside it, establishing no new
owner; `Rc::clone(&shared)` explicitly produces a new owner copy. The way to tell these two
intentions apart while reading code is to check whether the call carries the name `clone` —
the counter only reacts to a call carrying that name.

## Not Order, but Count, Decides

The previous lesson measured the **order** in which a container's contents ended — which
value ended before which determined the outcome. With reference counting the situation is
different: it is not which copy is dropped in which order that ends the value, only **how
many** copies remain that decides it.

**SO7.** — the claim that "the value ends when the last remaining copy is dropped, not when
a particular copy is" is confirmed in the run below: even though the originally built copy
of four is dropped in the middle, the ledger stays empty, and only fills when the last copy
goes.

```rust
// order_does_not_matter.rs — with Rc, it is not which copy is dropped but how many remain that matters
use std::cell::RefCell;
use std::rc::Rc;

type Ledger = Rc<RefCell<Vec<String>>>;

struct Trace {
    name: String,
    ledger: Ledger,
}

impl Trace {
    fn new(name: &str, ledger: &Ledger) -> Trace {
        Trace {
            name: name.to_string(),
            ledger: Rc::clone(ledger),
        }
    }
}

impl Drop for Trace {
    fn drop(&mut self) {
        self.ledger.borrow_mut().push(self.name.clone());
    }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(Vec::new()));
    let shared: Rc<Trace> = Rc::new(Trace::new("ends-when-last-copy-drops", &ledger));
    let a = Rc::clone(&shared);
    let b = Rc::clone(&shared);
    let c = Rc::clone(&shared);
    println!("counter after four copies: {}", Rc::strong_count(&a));

    drop(b);
    println!("counter once the middle copy is dropped: {}", Rc::strong_count(&a));
    println!("is the ledger still empty: {}", ledger.borrow().is_empty());

    drop(shared);
    println!("counter once the originally built copy is dropped: {}", Rc::strong_count(&a));
    println!("is the ledger still empty: {}", ledger.borrow().is_empty());

    drop(a);
    drop(c);
    let record = ledger.borrow();
    println!("record once the last copy is dropped too: {}", record.join(", "));
}
```

```
counter after four copies: 4
counter once the middle copy is dropped: 3
is the ledger still empty: true
counter once the originally built copy is dropped: 2
is the ledger still empty: true
record once the last copy is dropped too: ends-when-last-copy-drops
```

Of the four copies, `b` is dropped, then the originally built `shared` is dropped — a copy
that is neither first nor last by construction order, then the oldest copy itself. Even
after two drops the ledger stays empty, because two copies (`a` and `c`) still remain. The
value ends only once both of these are dropped too. In the previous lesson, a container's
contents ending depended on **the order** they were added in; here the end depends only on
**how many** copies remain — which copy's name is dropped in which order does not change the
outcome. This difference sums up the core distinction between the two container kinds: a
list or a `Box` records the **position** of a single ownership chain and is therefore
sensitive to order; an `Rc` records the **length** of the ownership chain and is therefore
sensitive only to count.

The explicit `drop` call used in the runs above carries forward the same explicit-call idea
seen in the Heap-Allocated Single Ownership lesson, but the outcome differs. There, an
explicit call on a `Box` ended the value itself early — there was no other option, since it
had a single owner. Here the same call only drops **one copy**; the value itself does not
end as long as another copy remains. The same `drop` function means a definite end under
single ownership, and a conditional decrement under shared ownership — its meaning shifts
with the ownership form of the value it is called on.

When the four runs come together, how reference counting generalizes the ownership rule
becomes clear. The counter is always updated from a single place, in a single order; cloning
multiplies access, not data; a reference never enters this count; and what decides the end
is the copies' quantity, not their identity. Together these four properties still provide an
end point that is deterministic and readable from the source — the instant the counter hits
zero shows up in the run just as precisely and repeatably as a single-owned value's scope
end did in the previous lesson.

## Summary

- `Rc<T>` is a container that places a value on the heap and counts the copies that access
  it.
- The counter rises with every `Rc::clone` call and falls with every copy's end.
- The value reaches its ledger only once the counter drops to zero — its life ends **with its
  container**, at the last remaining copy's end; this instant is readable from the source
  and always produced in the same order in the run.
- Taking a reference does not change the counter; only cloning does.
- The value's end depends not on which copy is dropped but on how many remain.
- `Rc::clone` multiplies not the moved value but the counter and the access to it; copies
  look at the same value.

## Next Step

The counter measured in this lesson was updated like an ordinary integer, in a single
context: every clone and every end ran from the same scope, in the same order. The next
lesson measures a type that does the same counting job but updates the counter itself in a
different way — the behavior stays the same, only how the counter is updated changes; in
which context this difference is needed stays outside this lesson's scope.
