Language & Runtime
Runtime Value Model
Last updated 2026-04-13
Runtime Value Model
This document defines the current internal guest-value and heap model used by the Rust runtime.
It is intentionally internal. None of these types are a public host API or a stable serialized contract by themselves.
Internal Guest Values
The runtime represents guest values with an internal Value enum:
- immediate scalars:
Undefined,Null,Bool,Number, andString - heap handles:
Object,Array,Map,Set, internalIterator,Closure, andPromise - callable built-ins:
BuiltinFunction - explicit host entry points:
HostFunction
The public host boundary does not expose Value directly. Host input, host
output, sidecar messages, and snapshot-adjacent APIs use StructuredValue
instead.
StructuredValue is tree-shaped: it preserves nested data, holes, and special
numeric tags, but it does not preserve guest object identity or alias graphs.
Heap Objects and Handles
Heap-backed state is stored indirectly through slotmap keys:
ObjectKeypoints toPlainObjectArrayKeypoints toArrayObjectMapKeypoints toMapObjectSetKeypoints toSetObjectIteratorKeypoints toIteratorObjectClosureKeypoints toClosurePromiseKeypoints toPromiseObjectEnvKeypoints to lexical environmentsCellKeypoints to mutable or immutable bindings
These handles are runtime-internal identities. Raw keys must never cross the
host boundary, appear in structured values, or be treated as stable serialized
references outside mustard.
Rooting and Reachability Rules
mustard now uses a non-moving mark-sweep collector over the existing slotmap
heaps. Heap handles remain stable for live allocations; collection only removes
unreachable entries and never relocates them.
The collector walks an explicit root set:
- the globals environment is always a root
- active call frames are roots
- each frame roots its current environment, scope stack, and operand stack
- async frames also root their backing promise object when present
- frame exception state also roots guest values and environments: pending exceptions, pending return/throw completions, and saved handler environments
- internal microtask jobs root saved async continuations and their settled values or rejections
- queued or suspended host requests root the guest promise they will settle
- environments root their cells
- cells root the
Valuethey contain - objects, arrays, maps, sets, and promises root their contained values
- iterators root the array they are currently traversing
- closures root their captured environment chain
- validated suspended snapshots restore the same runtime graph and therefore the same root categories after load
Practical rules that follow from this:
- no raw guest references may cross into JavaScript host code
- no raw guest references may cross the sidecar boundary
- no raw guest references may be embedded in structured host values
- snapshots may only persist validated runtime handles owned by
mustard
Collection currently runs at allocation-safe execution boundaries and resume points. That keeps the collector precise without requiring a moving handle layer or conservative native-stack scanning.
Internal Iterators
The current iteration milestone introduces one internal iterator kind:
- array iterators used by
for...of
These iterator objects are heap-allocated and snapshot-safe, but they are still runtime-internal:
- they are never exposed through
StructuredValue - they are not guest-authorable
- they currently preserve only an array reference and the next numeric index
- they allow suspended
for...ofloops to resume without rebuilding loop state - they do not implement user-visible iterator closing because generators and custom iterators are still deferred
Plain Objects, Arrays, Maps, Sets, and Shapes
Plain objects currently use IndexMap<String, Value>. Arrays use a dedicated
element vector plus string-keyed extra properties. Map and Set use
dedicated insertion-ordered entry vectors with SameValueZero key and membership
semantics.
For the current keyed-collection milestone:
Mapupdates preserve the original insertion position of an existing keySetignores duplicate adds without changing orderdeleteremoves the matching entry and compacts the internal orderclearempties the collection completely- iterable constructors and iterator-returning collection APIs remain deferred, so this order is currently an internal guarantee preserved for future expansion and snapshot correctness rather than a general guest iteration surface
There is no shape or hidden-class layer today. If shapes are introduced later, they are an optimization only:
- shapes must not define guest semantics
- shape absence must not change correctness
- property get/set behavior must remain centralized
Boundary Separation
The runtime keeps three layers distinct:
- internal guest values:
Value - host-safe transferred values:
StructuredValue - serialized runtime state: validated compiled programs and snapshots
That separation is part of the safety model. Guest semantics stay in Rust, and the Node wrapper stays limited to marshaling, capability dispatch, and error normalization.