Expand description
§gc_lang
A tracing garbage collector for interpreted-language runtimes.
gc-lang gives a runtime one focused thing: a heap it can allocate objects into
and periodically sweep, reclaiming everything no longer reachable — cycles
included. Allocate a value into a Heap and get back a Gc handle: a small,
Copy, generation-stamped reference that objects store to point at one another.
When the runtime hands its roots to Heap::collect, the collector traces the
graph those roots reach and frees the rest.
It is a mark-and-sweep collector, and it is entirely safe — the crate is
#![forbid(unsafe_code)]. Two design choices carry that:
- Reachability, not reference counting. Collection is decided by tracing from roots, so unreachable cycles are reclaimed. A runtime can build arbitrary object graphs — doubly-linked structures, back-edges, self-references — without leaking them.
- Handles, not pointers. A
Gcis an index plus a generation stamp, not an address. Objects wire to each other by handle, so the graph never fights the borrow checker, and a handle to a collected object resolves toNoneinstead of dangling. There is no use-after-free to have.
It owns object storage and reclamation only — no value representation, no interpreter, no scheduling of when to collect. The runtime decides what its roots are and when a collection is worth its cost.
§The Trace contract
An object type becomes collectable by implementing Trace: during the mark
phase the collector calls Trace::trace, and the object reports each Gc
handle it owns by calling Tracer::mark. Marking a handle keeps its object
alive; a handle you hold but never mark is treated as unreachable. Leaf objects
that own no handles implement trace as an empty body.
§Quickstart
A two-object graph: reclaim the unreachable one, keep the rooted one.
use gc_lang::{Gc, Heap, Trace, Tracer};
// The runtime's object type. It owns handles to other objects; `trace` reports them.
enum Value {
Number(f64),
Pair(Gc<Value>, Gc<Value>),
}
impl Trace for Value {
fn trace(&self, tracer: &mut Tracer<'_>) {
if let Value::Pair(a, b) = self {
tracer.mark(*a);
tracer.mark(*b);
}
}
}
let mut heap = Heap::new();
let one = heap.alloc(Value::Number(1.0));
let two = heap.alloc(Value::Number(2.0));
let pair = heap.alloc(Value::Pair(one, two));
let unused = heap.alloc(Value::Number(3.0));
assert_eq!(heap.len(), 4);
// Collect with `pair` as the only root. `pair`, `one`, and `two` are reachable;
// `unused` is not.
let stats = heap.collect([pair]);
assert_eq!(stats.freed, 1);
assert!(heap.get(unused).is_none());
assert!(heap.get(one).is_some());§no_std
The crate is no_std by default-compatible: it needs only alloc, and the
std feature (on by default) links the standard library without changing the
public surface. Disable default features to build in a no_std environment.
Structs§
- Collect
Stats - What a
collectpass did. - Gc
- A small, copyable, type-tagged handle to one object in a
Heap. - Heap
- A garbage-collected heap of
Tobjects, reclaimed by tracing mark-and-sweep. - Tracer
- The sink a
Traceimplementation reports its outgoing edges to.