pub enum Value {
Show 23 variants
Nil,
Void,
True,
False,
Number(isize),
Char(char),
Cons {
car: ArenaIndex,
cdr: ArenaIndex,
},
Symbol(ArenaIndex),
Lambda {
params: ArenaIndex,
body_env: ArenaIndex,
},
Builtin(Builtin),
StdLib(StdLib),
Array {
len: usize,
data: ArenaIndex,
},
Bytevector {
len: usize,
data: ArenaIndex,
},
String {
len: usize,
data: ArenaIndex,
},
Native {
id: usize,
},
Ref(ArenaIndex),
Usize(usize),
Syntax {
expr: ArenaIndex,
context: ArenaIndex,
},
ContFrame {
cont_data: ArenaIndex,
env: ArenaIndex,
},
ErrorObject {
message: ArenaIndex,
irritants_and_type: ArenaIndex,
},
Continuation {
cont_chain: ArenaIndex,
metadata: ArenaIndex,
},
Port(PortId),
Eof,
}Expand description
A Lisp value
§Memory Optimization
This enum inlines fixed-size data directly into variants to save arena slots and improve cache locality. Variable-length data (strings, arrays) still uses arena storage but with inline length for O(1) access.
Variants§
Nil
The empty list (NOT false - use False for that)
Void
Void/Unspecified value (R7RS)
Returned by side-effect-only forms like define, set!, display, etc.
The REPL should not print anything when this is returned.
True
Boolean true (#t)
False
Boolean false (#f) - the ONLY false value
Number(isize)
Integer number
Char(char)
Single character (used in strings and symbol storage)
Cons
Cons cell (pair) with inline car/cdr indices
Both car and cdr are stored inline, saving 2 arena slots per cons.
§Memory Savings
Previously: 1 slot for Cons + 2 slots for [Ref(car), Ref(cdr)] = 3 slots Now: 1 slot for Cons with inline car/cdr = 1 slot (saves 2 slots)
Use Lisp::cons(), Lisp::car(), Lisp::cdr() to create and access.
Symbol(ArenaIndex)
Symbol (contains a contiguous string) Points to a Value::String which contains the symbol name. The length is obtained from the String value, providing a single source of truth.
Lambda
Lambda / closure with inline params and body_env indices
params: ArenaIndex to list of parameter symbolsbody_env: ArenaIndex to a cons cell containing (body . env)
§Memory Savings
Previously: 1 slot for Lambda + 3 slots for [Ref(params), Ref(body), Ref(env)] = 4 slots Now: 1 slot for Lambda with inline params/body_env + 1 cons for body.env = 2 slots (saves 2 slots)
Use Lisp::lambda() to create and Lisp::lambda_parts() to extract.
Builtin(Builtin)
Built-in function (optimized)
StdLib(StdLib)
Standard library function (stored in static memory)
Unlike Lambda which stores code in the arena, StdLib references static function definitions. The function body is parsed on each call.
§Memory Efficiency
- Function definitions are in static memory (const strings)
- No arena allocation for the function definition itself
- Parsed AST is temporary and GC’d after evaluation
Array
Vector/Array with inline length and data pointer
Vectors store values contiguously in the arena. The length is inlined for O(1) access, saving 1 arena slot per array.
§Memory Layout
len: Number of elements (inline)data: Points directly to first element (no length header in arena)- Empty arrays have len=0 and data == NIL
§Memory Savings
Previously: 1 slot for Array + Number(len) header + elements Now: 1 slot for Array with inline len + elements only (saves 1 slot)
§Example
(define vec (make-vector 3 0)) ; Create vector of 3 zeros
(vector-set! vec 1 42) ; Set index 1 to 42
(vector-ref vec 1) ; => 42
(vector-length vec) ; => 3 (O(1) - inline!)
#(1 2 3) ; Vector literal syntaxBytevector
Bytevector (R7RS §6.9) with inline length and data pointer
Bytevectors store exact integers in the range 0–255 as Number values
contiguously in the arena, reusing the same layout as Array.
§Memory Layout
len: Number of bytes (inline)data: Points directly to firstNumbervalue in arena- Empty bytevectors have len=0 and data == NIL
§Example
#u8(0 10 5) ; Bytevector literal
(bytevector-length #u8(1 2 3)) ; => 3String
String with inline length and data pointer
Strings store characters contiguously in the arena. The length is inlined for O(1) access, saving 1 arena slot per string.
§Memory Layout
len: Number of characters (inline)data: Points directly to first Char value (no length header in arena)- Empty strings have len=0 and data == NIL
§Memory Savings
Previously: 1 slot for String + Number(len) header + chars Now: 1 slot for String with inline len + chars only (saves 1 slot)
§Example
(string-length "hello") ; => 5 (O(1) - inline!)
(string-ref "hello" 0) ; => #\hNative
Native function with inline id
Native functions are registered at runtime and identified by their ID. The actual function pointer is stored in the evaluator’s NativeRegistry.
Ref(ArenaIndex)
Raw arena index reference
Used internally for storing arena indices in contiguous blocks. This allows other variants (like Cons) to store their references in the arena rather than inline, enabling memory optimizations.
§Note
This is an internal implementation detail and should not be exposed to Lisp code directly. It’s traced by the GC like any other reference.
Usize(usize)
Unsigned integer (internal use)
Used for storing unsigned values like array lengths, indices, or other internal counters that need the full positive range of a machine word.
§Note
This is primarily an internal implementation detail. For user-facing
integers, prefer Number(isize) which supports negative values.
Syntax
Syntax object for procedural macros (syntax-case)
A syntax object wraps an expression with lexical context information
for hygienic macro expansion. This is the foundation for implementing
R6RS-style syntax-case macros.
§Memory Layout
expr: ArenaIndex to the wrapped datum (the actual S-expression)context: ArenaIndex to cons cell (marks . substitutions)- car: list of marks for tracking hygiene scopes
- cdr: substitution environment for identifier resolution
This maintains the 2-index constraint per arena slot, matching Lambda’s layout.
§Example
(syntax-case stx ()
((keyword arg ...)
(with-syntax ((name (generate-name)))
#'(define name (lambda () arg ...)))))§References
- R6RS Chapter 11 (syntax-case)
- “Macros that Work” (Clinger & Rees, 1991)
- psyntax (Dybvig, Hieb, Bruggeman)
ContFrame
Continuation frame for arena-based continuation stack (call/cc support)
Continuation frames form a linked list in the arena, enabling O(1) capture for call/cc. Each frame stores the continuation type, associated data, and a reference to the parent continuation.
§Memory Layout
cont_data: ArenaIndex to cons cell((type . data) . parent_cont)- car: cons cell
(Usize(cont_type) . data)where data encodes continuation-specific values - cdr: ArenaIndex to parent ContFrame, or Nil for Done
- car: cons cell
env: ArenaIndex to the environment at this continuation point
This maintains the 2-index constraint per arena slot, matching Lambda’s layout.
§Example Continuation Types (encoded as Usize)
- 0: Done - computation complete
- 1: ApplyForced - after evaluating function
- 2: IfBranch - after evaluating condition
- etc.
§References
See docs/CALL_CC_IMPLEMENTATION_PLAN.md for the full implementation plan.
ErrorObject
R7RS error object (§6.11)
Created by the error procedure. Stores the error message and
associated irritant values for structured exception handling.
§Memory Layout
message: ArenaIndex to a Value::String or Value::Symbol containing the error messageirritants_and_type: ArenaIndex to a cons cell(irritants . error_type)- car: list of irritant values passed to
error - cdr: error type (Nil for standard
(error msg ...)calls)
- car: list of irritant values passed to
§Example
(error "out of range" 42) ; message="out of range", irritants=(42), type=()
(guard (e ((error-object? e) (error-object-message e)))
(error "bad value" 1 2 3)) ; => "bad value"Continuation
Captured continuation from call/cc - a first-class callable value
When call-with-current-continuation (call/cc) is invoked, the current
continuation is captured and wrapped as this value type. The continuation
can later be invoked as a procedure, which abandons the current computation
and returns to the point where the continuation was captured.
§Memory Layout
Following the 2-index constraint like Lambda and Cons:
cont_chain: ArenaIndex to ContFrame linked list (captured continuation stack)- Points to the head of the continuation chain (most recent frame)
- Nil represents an empty continuation (e.g., at top-level REPL with no pending work)
metadata: ArenaIndex to cons cell(capture_env . dynamic_wind_chain)- car: environment at capture point
- cdr: dynamic-wind chain for proper before/after thunk invocation
§Semantics
When a continuation is called with a value:
- The current computation is abandoned
- The captured continuation stack is restored
- The value becomes the result at the call/cc point
§Example
(+ 1 (call/cc (lambda (k) (+ 2 (k 3)))))
;; => 4 (not 6, because (k 3) never returns)§References
See docs/CALL_CC_IMPLEMENTATION_PLAN.md for the full implementation plan.
Port(PortId)
I/O Port (R7RS §6.13)
A first-class port value identified by a PortId.
Standard ports (stdin=0, stdout=1, stderr=2) are predefined;
additional ports can be opened for string or file I/O.
Eof
End-of-file object (R7RS §6.13)
A unique value returned by read operations when the end of input is reached.
Implementations§
Source§impl Value
impl Value
Sourcepub const fn is_void(&self) -> bool
pub const fn is_void(&self) -> bool
Check if this value is void (unspecified value)
Void is returned by side-effect-only forms like define, set!, display.
The REPL should not print anything when this value is returned.
Sourcepub const fn is_false(&self) -> bool
pub const fn is_false(&self) -> bool
Check if this value is false (#f) This is the ONLY way to be false in this Lisp
Sourcepub const fn is_boolean(&self) -> bool
pub const fn is_boolean(&self) -> bool
Check if this value is a boolean (#t or #f)
Sourcepub const fn is_integer(&self) -> bool
pub const fn is_integer(&self) -> bool
Check if this value is an integer
Sourcepub const fn as_ref(&self) -> Option<ArenaIndex>
pub const fn as_ref(&self) -> Option<ArenaIndex>
Extract ArenaIndex from a Ref value. Returns None if not a Ref.
Sourcepub fn unwrap_ref(self) -> ArenaIndex
pub fn unwrap_ref(self) -> ArenaIndex
Extract ArenaIndex from a Ref value, panicking if not a Ref. Use only when you are certain the value is a Ref (e.g., after alloc_contiguous for Refs).
Sourcepub const fn is_builtin(&self) -> bool
pub const fn is_builtin(&self) -> bool
Check if this value is a builtin
Sourcepub const fn is_procedure(&self) -> bool
pub const fn is_procedure(&self) -> bool
Check if this value is a procedure (lambda, builtin, stdlib, or native function)
Sourcepub const fn is_bytevector(&self) -> bool
pub const fn is_bytevector(&self) -> bool
Check if this value is a bytevector
Sourcepub const fn is_ref(&self) -> bool
pub const fn is_ref(&self) -> bool
Check if this value is a ref (internal arena index reference)
Sourcepub const fn is_usize(&self) -> bool
pub const fn is_usize(&self) -> bool
Check if this value is a usize (internal unsigned integer)
Sourcepub const fn is_cont_frame(&self) -> bool
pub const fn is_cont_frame(&self) -> bool
Check if this value is a continuation frame
Sourcepub const fn is_continuation(&self) -> bool
pub const fn is_continuation(&self) -> bool
Check if this value is a captured continuation (from call/cc)
Trait Implementations§
Source§impl<const N: usize> Trace<Value, N> for Value
Implement Trace for GC support
impl<const N: usize> Trace<Value, N> for Value
Implement Trace for GC support
With inline fields, tracing is simpler - we just trace the ArenaIndex fields directly. No arena access needed to determine structure, which improves GC performance.
Source§fn trace<F>(&self, tracer: F)where
F: FnMut(ArenaIndex),
fn trace<F>(&self, tracer: F)where
F: FnMut(ArenaIndex),
ArenaIndex references contained in this value. Read more