Every piece of the copying collector comes together into one Collect call. Today you run it on a graph with a doubly-referenced node and watch the survivors compact, references update, and garbage vanish.
Assemble forward-roots, scan, and flip into Collect, and assert the compacted result.
Collect is the copying collector in four steps: reset the to-space cursor, forward
the roots (copying each rooted object and recording its new id), scan to-space to
copy the rest and rewrite every field, flip the spaces, and repoint the roots at the
copies. Forwarding the roots in sorted order keeps the compacted ids deterministic -
the roots land first, then their children in scan order.
The result is everything Chapter 4 promised. The survivors that were scattered at slots
0, 2, 4 come out packed at 0, 1, 2, with all the free space in one block -
compaction for free. The doubly-referenced s is copied once and both of a’s
fields point at that single copy, because forwarding pointers guarantee copy-once. The
garbage is not swept or freed one object at a time; it is simply left behind in the
abandoned space, so collection time is proportional to the live data, not the garbage.
You now have two complete collectors - mark-sweep and copying - built on the same object
model. The final chapter adds the refinements real collectors need and proves both on
one graph.
func (h *CopyingHeap) Collect() {h.toNext = 0newRoots := map[Ref]bool{}for _, r := range h.Roots() { // sorted, so copy order is deterministicnewRoots[h.forward(r)] = true}h.scan() // copy the rest, rewrite every fieldh.flip() // swap spaces, reset cursorsh.roots = newRoots // roots now name the copies}