---
title: 'Heap-Allocated Single Ownership'
source: 'https://academia.sh/en/courses/ownership/heap-allocated-ownership'
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 heap-allocated value keeps exactly one owner even when it is moved'
    - 'Compares the end order of a single value inside a container with that of the values in a list'
    - 'Demonstrates that heap-allocated single ownership makes a self-referential type possible'
---

# Heap-Allocated Single Ownership

How a value can still have exactly one owner while it lives on the heap, how a value that ends together with its container depends on the container's own order, and how that single ownership makes a self-referential type possible.

The References in Structs lesson closed by measuring that a struct either moves a piece of
data or borrows it, and that in both cases exactly one owner exists. This lesson's question
targets that very assumption: can a value have more than one owner? The answer itself is not
coming yet — first it is not the **number** of owners that changes, but **where** a
single-owned value sits. This intermediate step is necessary, because understanding how
shared ownership will be measured first requires knowing how a single-owned value behaves
on the heap. The measurement axis does not change: it still asks where a value's life ends,
not how a running unit stops the way the Go curriculum's Concurrency course asks.

This course's opening lesson treated whether a value sits on the stack or the heap as a
third repetition — after How Computers Work and Production Go: Testing, Tooling and
Performance; memory theory closed there and is not reopened here. That same lesson built
four classes: a value ends at scope end, where it was moved, with an explicit call, or
together with its container. This lesson asks: when a value sits on the heap, which of
these four classes does it fall into, and does the ownership rule still hold? `Box` is a
container that moves a value onto the heap without ever changing the ownership count — that
is the first thing this lesson measures.

## The Container Is a Class Too, But What Is Inside Is Singular

Ending "together with its container" does not come in a single form. A list carries several
values at once; `Box` carries exactly one. Both fall into the container class, in both the
content ends together with the container, but the container's own end order is not the same.

This lesson's measurement also uses the `Trace`/`ledger` rig set up at the course's opening:
every `Trace` value appends its own name to the ledger when it is dropped, the `Drop`
implementation that makes this possible is not rewritten here, only excerpted. The run below
holds a three-element list and a single `Box` value in the same scope, and prints the order
the ledger receives as the scope closes.

**SO1.** — the claim that "the single value on the heap ends before the list" is confirmed
by the `end order` line of the run below: the name `on-heap-single` comes before the list's
three elements.

```rust
// container_classes.rs — the class of ending with a container: a list and a single value on the heap
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 mut list = Vec::new();
        list.push(Trace::new("list-1", &ledger));
        list.push(Trace::new("list-2", &ledger));
        list.push(Trace::new("list-3", &ledger));
        let _on_heap = Box::new(Trace::new("on-heap-single", &ledger));
        println!("ledger length while the container is full: {}", ledger.borrow().len());
    }
    let record = ledger.borrow();
    println!("end order: {}", record.join(", "));
}
```

```
ledger length while the container is full: 0
end order: on-heap-single, list-1, list-2, list-3
```

When the scope closes, bindings end starting from the one bound last: `_on_heap` was bound
after `list`, so it is the first to end. `list` ends next, but the three `Trace` values inside
the list end in the **order they were added**. The end order of bindings and the end order
of a container's elements are not the same rule — one follows the reverse of declaration, the
other follows insertion order; because `Box` is a single-element container, this second rule
never shows up in it — only the first rule ever applies.

## Living on the Heap Does Not Change How a Value Is Used

A value sitting on the heap does not change how it is accessed. A struct's fields and methods
inside a `Box` are reached as if the value sat right there; no separate step is written to
open the container in between.

```rust
// box_access.rs — access through a box works as if the value were right there
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let point = Box::new(Point { x: 3, y: 4 });
    println!("x: {}, y: {}", point.x, point.y);
    let total = point.x + point.y;
    println!("total: {}", total);
}
```

```
x: 3, y: 4
total: 7
```

The lines `point.x` and `point.y` are written the same way whether `point`'s type is `Point`
or `Box<Point>`. The run below compares this directly: the same `Point` value is read once
without being boxed, and once boxed.

```rust
// access_comparison.rs — boxed and unboxed access are written on the same line
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let direct = Point { x: 1, y: 2 };
    let on_heap = Box::new(Point { x: 1, y: 2 });
    println!("direct: x={}, y={}", direct.x, direct.y);
    println!("on-heap: x={}, y={}", on_heap.x, on_heap.y);
}
```

```
direct: x=1, y=2
on-heap: x=1, y=2
```

The only difference between the two lines is how the value was built (`Point { ... }`
versus `Box::new(Point { ... })`); the field-reading lines are identical. Living on the heap
only decides **where** a value is held; it does not change how the value is read and used.
This is a pattern the shared containers ahead will keep following too: whatever the
container's type, access to the value inside it is always written on the same line.

## Boxing a Value Does Not Multiply Ownership

The Ownership and Moves lesson measured that a value moved into a function, into an inner
scope, or into a struct's field carries its end point along with it, but never changes its
count. The same principle holds for a value on the heap: no matter how many times a `Box`
value changes hands, the `Trace` it carries ends only **once**.

The run below passes a single `Box<Trace>` value through two separate function calls and
leaves it in the last owner's hands.

**SO2.** — the claim that "even after three changes of hands, exactly one end record is
produced" is confirmed by the `total record count: 1` line of the run below.

```rust
// box_move.rs — moving a boxed value does not change the single ownership count
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 to_another_scope(received: Box<Trace>) -> Box<Trace> {
    // Ownership is taken over here, and the same value is returned.
    received
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(Vec::new()));
    {
        let first = Box::new(Trace::new("single-owner", &ledger));
        let second = to_another_scope(first);
        let third = to_another_scope(second);
        println!("ledger length after three moves: {}", ledger.borrow().len());
        println!("final owner: {}", third.name);
    }
    let record = ledger.borrow();
    println!("total record count: {}", record.len());
    println!("records: {}", record.join(", "));
}
```

```
ledger length after three moves: 0
final owner: single-owner
total record count: 1
records: single-owner
```

The binding named `first` moves into the `to_another_scope` function, comes back out under
the name `second`, then moves into the same function again and comes back as `third`. Three
bindings, three separate names, but all of them are the single owner of the same `Trace`
value at different times. The ledger's length being zero as `third` ends shows that **neither**
of the two earlier moves produced an end: only when the final owner's scope closes does a
record open. A single `single-owner` record appearing in the ledger confirms that boxing
does not **multiply** ownership — moving only updates who owns the value, it does not open a
new record.

This result might not seem unexpected at first, but thinking through the opposite case makes
the distinction clear: if ownership multiplied, every call to `to_another_scope` would
produce a separate end record in the ledger, and three records total would appear. The
"single owner" rule established for a value on the stack in the Ownership and Moves lesson
is not broken when the value moves onto the heap; what moves is not the value itself, only the
right to own that value.

## A Box Can Also End Early With an Explicit Call

A value on the heap does not always have to wait for its container to close. The Borrowing
lesson measured that an explicit `drop` call ends a value's life early; the same call works
on a `Box` too. This shows that the difference between the "with its container" and "with an
explicit call" classes comes **not from the type**, but from how the value is ended.

**SO3.** — the claim that "the value dropped early ends before the value that stays in
scope" is confirmed by the `end order` line of the run below.

```rust
// early_drop.rs — a boxed value can be dropped early with an explicit call
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 early = Box::new(Trace::new("early-dropped", &ledger));
        let remaining = Box::new(Trace::new("scope-retained", &ledger));
        drop(early);
        println!("ledger length after the explicit call: {}", ledger.borrow().len());
        println!("remaining is still readable: {}", remaining.name);
    }
    let record = ledger.borrow();
    println!("end order: {}", record.join(", "));
}
```

```
ledger length after the explicit call: 1
remaining is still readable: scope-retained
end order: early-dropped, scope-retained
```

Even though `early` is bound before `remaining`, it ends without waiting for scope end;
`remaining` follows the ordinary scope-end rule. The same type — `Box<Trace>` — can fall
into two different end classes; what decides the class is not the container's type, but what
is done to that value in the source.

When this lesson's three measurements come together, a `Box` value can be seen entering
three of the four classes built at the course's opening: if its scope closes naturally, it
ends with its container (the Container Is a Class Too section); if it moves into a function
or another binding, its end point moves with it (the Boxing a Value section); if it is
dropped with an explicit call, it ends with an explicit call (this section). It can never
fall into the scope-end class alone, because a value on the heap is always either inside a
container or the subject of a move — it never stands on its own the way an ordinary binding
on the stack does.

## A Recursive Type: The Door the Heap Opens

The Data Structures course established self-referential structures such as linked lists and
trees as a concept; this lesson does not re-teach that structure, it only measures that
heap-allocated single ownership **makes** such a type possible.

A definition where each link of a chain carries the next link of its own type directly —
without a container — is rejected by the compiler: when each link contains the next one
directly, the type's own size is never considered settled, because the number of links can
never be read from the source as a fixed bound. This is a different problem from a function
calling itself: function recursion proceeds step by step at runtime, but a type's definition
must be read in a single pass at compile time — a definition that keeps unfolding inside
itself can never finish that reading.

The rule's plainest form can be summed up like this: a definition that writes the `Record`
type directly into a `Record` struct's `next` field is never accepted, because `next` is
again a `Record`, and this definition never closes inside itself. A definition that writes
`Box<Record>` into the same field is accepted, because `Box` is a separate container of
fixed shape that lives on the heap — the definition places not a direct `Record` inside
`Record`, but the fixed-shape container itself. The accepted form carries the next link the
same exact way: not directly, but through a `Box`, and that is why the chain can run as long
as it needs to.

**SO4.** — the claim that "the chain's end order proceeds in the same order it was built" is
confirmed by the `end order: link-a, link-b, link-c` line of the run below — the order does
not follow the reversal seen in binding ends.

```rust
// chain.rs — the door the box opens: a type that holds itself inside itself
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());
    }
}

enum Chain {
    Empty,
    Node(Trace, Box<Chain>),
}

fn length(c: &Chain) -> usize {
    match c {
        Chain::Empty => 0,
        Chain::Node(trace, next) => {
            println!("visited link: {}", trace.name);
            1 + length(next)
        }
    }
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(Vec::new()));
    {
        let chain = Chain::Node(
            Trace::new("link-a", &ledger),
            Box::new(Chain::Node(
                Trace::new("link-b", &ledger),
                Box::new(Chain::Node(
                    Trace::new("link-c", &ledger),
                    Box::new(Chain::Empty),
                )),
            )),
        );
        println!("chain length: {}", length(&chain));
    }
    let record = ledger.borrow();
    println!("end order: {}", record.join(", "));
}
```

```
visited link: link-a
visited link: link-b
visited link: link-c
chain length: 3
end order: link-a, link-b, link-c
```

This order might look surprising at first, because the rule measured earlier in this lesson
was that bindings end starting from the one bound last. But here there is only a single outer
binding — `chain` — not three separate bindings. When `chain` ends, its own `Trace` field
ends first (`link-a`), then its own `Box<Chain>` field ends; that `Box`, in turn, ends its
own inner value first by ending its `Trace` field (`link-b`), then its own `Box`, and so on.
Because the end is a single chained call proceeding from outside in, it proceeds in the same
direction as construction — a different end shape from the list example in the Container Is
a Class Too section, where there were many independent bindings; here there is a single
nested chain of bindings.

When the four measurements come together, one picture emerges: `Box` is a container that
changes **where** a value sits, but never changes **how many owners** it has. Access to the
value stays the same, moving carries the end point but does not multiply the count, an
explicit call can end it early, and it is this single ownership that lets a type carry the
next link of its own kind. The next question's ground is laid right here: in none of these
four measurements did the ownership count ever exceed one — it was always a single binding,
at a single time, owning that value.

## Summary

- A value has exactly one owner even while it sits on the heap; `Box` does not change that
  count.
- A single value inside a container and the values in a list follow different end-order
  rules: one follows the reverse of declaration, the other follows insertion order.
- Living on the heap does not change how a value is read and used.
- A value on the heap produces exactly one end record in the ledger even when it moves.
- The same type can fall into either the "with its container" or "with an explicit call"
  class, depending on what is done to it in the source.
- Heap-allocated single ownership makes a self-referential type compile; a chain type's end
  order can then proceed in the same direction as its construction.

## Next Step

In this lesson a value on the heap kept a single owner throughout — whether it moved, was
dropped early, or was chained. The next lesson lifts that assumption: a mechanism where
several bindings can own the same value at once — and how that sharing is tracked with a
counter readable from the source.
