pub enum Value {
Show 22 variants
Int(i64),
Float(f64),
Bool(bool),
Str(SmolStr),
Bytes(Vec<u8>),
Unit,
List(VecDeque<Value>),
Tuple(Vec<Value>),
Record {
shape_id: u32,
fields: Box<IndexMap<SmolStr, Value>>,
},
StackRecord {
shape_id: u32,
slab_start: u32,
field_count: u16,
},
StackTuple {
slab_start: u32,
arity: u16,
},
ArenaRecord {
shape_id: u32,
slab_start: u32,
field_count: u16,
},
ArenaTuple {
slab_start: u32,
arity: u16,
},
Variant {
name: String,
args: Vec<Value>,
},
Closure {
fn_id: u32,
body_hash: BodyHash,
captures: Vec<Value>,
},
F64Array {
rows: u32,
cols: u32,
data: Vec<f64>,
},
Map(BTreeMap<MapKey, Value>),
Set(BTreeSet<MapKey>),
Deque(VecDeque<Value>),
Actor(Arc<Mutex<ActorCell>>),
Ticker(Arc<AtomicBool>),
ArrowTable(Arc<RecordBatch>),
}Variants§
Int(i64)
Float(f64)
Bool(bool)
Str(SmolStr)
String value. SmolStr stores strings ≤ 22 bytes inline — no heap
allocation for identifiers, HTTP methods, status codes, short keys, etc.
Clone of a short SmolStr is a 24-byte stack copy (#389 slice 4).
Bytes(Vec<u8>)
Unit
List(VecDeque<Value>)
Tuple(Vec<Value>)
Record
Record literal. shape_id is the Program::record_shapes
index of the field-name vec the record was built from
(#462 slice 2), so the Op::GetField polymorphic IC can
match on a single u32 compare instead of walking the
IndexMap by name. Records constructed outside the bytecode
(JSON decode, SQL row → record, HTTP request mutators, test
fixtures) have no compile-time shape and carry NO_SHAPE_ID
— the IC unconditionally misses on them and falls through to
the existing name walk.
fields is Box<IndexMap> rather than IndexMap inline
because the bare IndexMap is ~56B; inlining it plus
shape_id would push Value’s enum size from 64B → 72B,
which measurably regresses the VM stack push/pop loop
(Value is cloned/moved on every push/pop). Boxing keeps
Value::Record at 16B and Value at the pre-#462 64B.
The indirection on every IndexMap access costs a few ns
but the IC drops the field-name string compare on every
hit, which is the net win on mono_chain.
shape_id is not part of structural equality (see
PartialEq below): two records with identical fields must
compare equal regardless of provenance, so a JSON-decoded
record equals a compile-time-built one with the same fields.
StackRecord
Frame-local record (#464 step 2). Emitted by
Op::AllocStackRecord at sites the escape analysis proved
can’t outlive the current call frame. slab_start indexes
into Vm::stack_record_arena; the field_count consecutive
values starting there are the record’s fields, in
Program.record_shapes[shape_id] order (same insertion order
as Op::MakeRecord uses, so the polymorphic-IC offset is
interoperable with Value::Record).
Op::GetField is the only consumer that knows how to read
these — every other observation point (Op::Return,
Op::Call, Op::MakeRecord as a field value, …) is an
escape op that the analysis prevents this variant from
reaching. If a StackRecord ever does reach an unexpected
site (escape-analysis bug), it surfaces as a panic at the
boundary, not undefined behavior — the arena is plain
Vec<Value> in safe Rust.
Size: 4 (shape_id) + 4 (slab_start) + 2 (field_count) = 10
bytes payload + tag, comfortably inside the 64B Value
envelope.
StackTuple
Frame-local tuple (#464 tuple codegen). The stack-alloc
analogue of Value::Tuple, emitted by Op::AllocStackTuple at
sites the escape analysis proved can’t outlive the current
frame. slab_start indexes into Vm::stack_record_arena (the
arena is shared with StackRecord — both are flat Value
slabs released together on Op::Return); the arity
consecutive values starting there are the tuple elements in
positional order.
Like StackRecord, the only consumer that knows how to read
these is Op::GetElem — every other observation point
(Return, Call, a MakeTuple/MakeRecord field value,
equality, JSON) is an escape op the analysis prevents this
variant from reaching. An unexpected arrival surfaces as a
panic at the boundary, not UB (the arena is safe Vec<Value>).
ArenaRecord
Request-scoped arena record (#463 slice 2a). Same handle shape
as Value::StackRecord but indexes Vm::arena_slab (request
lifetime) instead of Vm::stack_record_arena (frame lifetime).
Emitted by Op::AllocArenaRecord at sites
arena::build_arena_index proves do not escape the request
scope opened by EffectHandler::enter_request_scope. Reads via
Op::GetField (polymorphic across Record / StackRecord /
ArenaRecord).
Inspection paths (to_json, equality, memo hash, generic
clone) defensively panic on this variant, same contract as
StackRecord. Slice 1’s arena::build_arena_index analysis
proves these paths are unreachable in well-routed code (any
reach is a soundness bug — analysis or codegen). The
scoping doc (docs/design/arena-plumbing.md § “Arena handles
MUST be readable at serialization”) flags this as the place
where arena diverges from #464: a future slice will materialize
arena handles at the response-serialization boundary so the
Response’s to_json reads through to the slab. That
materialization is out of scope for slice 2a; today
arena ops only ship for hand-crafted bytecode tests, which
avoid the inspection paths.
ArenaTuple
Request-scoped arena tuple (#463 slice 2a). Tuple analogue of
ArenaRecord; same lifetime / fallback / inspection-panic
contract.
Variant
Closure
First-class function value (a lambda + its captured locals). The
function’s first captures.len() params bind to captures; the
remaining params are supplied at call time.
fn_id is a dense compile-time index into Program::functions
for fast dispatch; body_hash is the canonical identity —
two closures with identical bytecode bodies compare equal even
when their fn_ids differ (which they will, when the source
has the same closure literal at two locations). See PartialEq
below and #222 for the rationale.
F64Array
Dense row-major f64 matrix. A “fast lane” representation that
avoids the per-element Value::Float boxing of Value::List.
Used by Core’s native tensor ops (matmul, dot, …) so end-to-end
matmul perf hits the §13.7 #1 100ms target without paying for
2M Value boxings at the call boundary.
Map(BTreeMap<MapKey, Value>)
Persistent map keyed by MapKey (Str or Int). Insertion-
independent equality (sorted by BTreeMap’s Ord), so two
maps built from the same pairs in different orders compare
equal. Restricting keys to two primitive variants keeps
Eq + Hash requirements off Value itself, which has
closures and floats and can’t be hashed soundly.
Set(BTreeSet<MapKey>)
Persistent set with the same key-type discipline as Map.
Deque(VecDeque<Value>)
Double-ended queue. O(1) push/pop on both ends; otherwise
behaves like List for iteration / equality / JSON shape.
Lex’s type system tracks Deque[T] separately from List[T]
so users explicitly opt in to deque semantics; the runtime
uses this dedicated variant rather than backing a deque on top
of Value::List (which would make push_front O(n)).
Actor(Arc<Mutex<ActorCell>>)
A handle to a conc.Actor. The Arc<Mutex<ActorCell>> allows
cheap cloning and safe concurrent access — the mutex serialises
message delivery so the actor processes one message at a time.
Two actor handles compare equal iff they point to the same cell
(identity equality, not structural equality).
Ticker(Arc<AtomicBool>)
A periodic-tick handle returned by conc.every (#445). The
AtomicBool is the cancel flag — conc.cancel(t) sets it and
the background scheduler thread observes it on its next iteration
and exits. Two ticker handles compare equal iff they point to the
same cancel flag.
ArrowTable(Arc<RecordBatch>)
Apache Arrow RecordBatch — an unboxed columnar table. The
“fast lane” representation for lex-frame and any future
dataframe code: a Value::ArrowTable with one int64 column
of N rows is N×8 bytes of contiguous memory, not N
Value::Int(_) enum tags inside a VecDeque. Reductions
(arrow.col_sum_int, arrow.col_mean, …) execute as one
Rust call over the flat buffer, bypassing the bytecode VM
for the inner loop.
Arc makes clone cheap (refcount bump) — Arrow tables are
already immutable so structural sharing across closures is
safe. Equality is structural over schema + columns.
Implementations§
Source§impl Value
impl Value
pub fn as_int(&self) -> i64
pub fn as_float(&self) -> f64
pub fn as_bool(&self) -> bool
pub fn as_str(&self) -> &str
Sourcepub fn contains_arena_record(&self) -> bool
pub fn contains_arena_record(&self) -> bool
Returns true if this value is, or transitively contains, an
ArenaRecord or ArenaTuple. Used by the memo gate to skip
memoization when request-scoped arena handles are present in the
call arguments — such values cannot be safely hashed because the
memo cache outlives the request arena (#621).
Sourcepub fn to_json(&self) -> Value
pub fn to_json(&self) -> Value
Render this Value as a serde_json::Value for emission to
CLI output, the agent API, conformance harness reports, etc.
Canonical mapping shared across crates; previously every
boundary had its own copy.
Encoding:
Variant { name, args }→{"$variant": name, "args": [...]}F64Array { ... }→{"$f64_array": true, rows, cols, data}Closure { body_hash, .. }→"<closure HEX8>"(first 8 hex chars of the body hash; equivalent closures across source locations render identically — see #222)Bytes→{"$bytes": "deadbeef"}(lowercase hex). Round-trips throughfrom_json. Bare hex strings decode asStr, so the marker is required to disambiguate bytes from a string that happens to look like hex.Mapwith all-Strkeys → JSON object; otherwise array of[key, value]pairs (Int keys can’t be JSON-object keys)Set→ JSON array of elements- other variants → their natural JSON shape
Note: this form is not round-trippable for traces (see
lex-trace’s recorder, which uses a richer marker form).
Sourcepub fn from_json(v: &Value) -> Value
pub fn from_json(v: &Value) -> Value
Decode a serde_json::Value into a Value. The inverse of
to_json for the shapes Lex round-trips:
{"$variant": "Name", "args": [...]}→Value::Variant{"$bytes": "deadbeef"}→Value::Bytes(lowercase hex; an odd-length string or non-hex character falls through toValue::Record, matching the malformed-$variantfallback)- JSON object →
Value::Record - JSON array →
Value::List - JSON null →
Value::Unit - JSON string / bool / number → the corresponding scalar
Map, Set, F64Array, and Closure don’t round-trip — they decode as their natural JSON shape (Object / Array / Object / Str respectively), since the CLI / HTTP / VM callers building Values from JSON don’t have those shapes in their input vocabulary.
Sourcepub fn record_dynamic(fields: IndexMap<String, Value>) -> Value
pub fn record_dynamic(fields: IndexMap<String, Value>) -> Value
Build a Value::Record whose fields don’t come from an
Op::MakeRecord site — JSON decode, SQL row → record, host
effect handlers, test fixtures, etc. Interns the field-name
set in the process-global shape registry (#462 slice 3) so
records with the same set of field names share a stable
shape_id and hit the same IC slot. Two records with the
same fields in different insertion order share a shape_id
(the registry sorts the field-name vec before lookup),
matching the existing Value::Record structural-equality
semantics.
Dynamic shape IDs live in the high half of the u32 range
(see crate::shape_registry::DYNAMIC_SHAPE_ID_BASE) so they
can’t collide with the per-program shape indices emitted by
Op::MakeRecord. Mixed-flavor IC sites (which the slice-2b
measurement found at exactly zero occurrences) would still
be correct under the IC’s shape-keyed verifier — they’d just
churn the cache.
Build a Record from a String-keyed host map (JSON decode, SQL
rows, builtins). Keys are re-collected into interned SmolStr
(#461 field-name interning). The hot bytecode MakeRecord path
builds SmolStr-keyed maps directly and never routes through
here; callers that already hold an interned map use
record_interned.
Trait Implementations§
Source§impl PartialEq for Value
Manual PartialEq for Value (#222). Mirrors the auto-derived
implementation for every variant except Closure, which compares
on (body_hash, captures) only — fn_id is a dense compile-time
index that is not stable across source-location-equivalent closure
literals, and including it would defeat the canonicality property
the body_hash field exists to provide.
impl PartialEq for Value
Manual PartialEq for Value (#222). Mirrors the auto-derived
implementation for every variant except Closure, which compares
on (body_hash, captures) only — fn_id is a dense compile-time
index that is not stable across source-location-equivalent closure
literals, and including it would defeat the canonicality property
the body_hash field exists to provide.