Objects become a graph only when they can point at each other. Today you build the field accessors - SetField writes another object's id into a field, GetField reads it back - which is how every edge in the object graph is stored.
Store one object's id in another object's field and read it back.
An object’s fields are references: each field either holds the nil reference
or the id of another object. Writing b into field 0 of a records an edge in
the object graph “a points at b” as a plain integer, the same way a real object would
hold a pointer to another. SetField stores the id; GetField reads it back.
This is the whole vocabulary the collector needs. Reachability, marking, and copying
will all be phrased in terms of “follow object a’s fields to the objects it
references.” Note that a field you never set stays Nil - an object referencing
nothing through that slot - which the collector will treat as a dead end when it
traces. Next you will list the live objects, then gather the roots.
func (h *Heap) SetField(obj Ref, i int, val Ref) {h.slots[obj].fields[i] = val // store the referenced object's id in field i}func (h *Heap) GetField(obj Ref, i int) Ref {return h.slots[obj].fields[i]}