Skip to main content

Value

Enum Value 

Source
pub enum Value {
    I64(i64),
    BigInt(Rc<BigInt>),
    Symbol(Text),
    Str(Text),
    List(Vector<Value>),
    Closure {
        id: u64,
        code: Rc<Lambda>,
        applied: u8,
        env: Env,
    },
    Map(HashTrieMap<Value, Value>),
    Builtin {
        op: Builtin,
        args: Vec<Value>,
    },
    HostFn {
        code: Rc<HostFn>,
        args: Box<[Value]>,
    },
    Module(Rc<EnvNode>),
    Iota {
        home: Rc<EnvNode>,
        index: u32,
    },
    Object(Rc<ObjectData>),
}
Expand description

A runtime value.

Variants§

§

I64(i64)

A machine-word integer — the canonical representation for any integer that fits in i64. Inline (one word, no heap), so the common arithmetic path neither allocates nor sets the enum width. The invariant “an integer is Value::BigInt iff it does not fit i64” is what makes equality and hashing well-defined (no value has two representations); it is maintained by routing every wide result through Value::from_bigint.

§

BigInt(Rc<BigInt>)

An arbitrary-precision integer, used only for magnitudes outside i64. Behind an Rc so the ~4-word BigInt does not widen Value (one word) and cloning a wide value is a refcount bump rather than a digit-vector copy — the big ints are immutable, so sharing is sound. A value here is always |n| > i64::MAX by the canonical invariant (see Value::I64).

§

Symbol(Text)

A symbol literal, stored without its dot (foo for .foo) as an owned cheap-clone Text; the dot is re-added when displayed.

§

Str(Text)

An owned, immutable, cheap-clone UTF-8 string (Text). Equality and order are Rust’s &str (byte order over UTF-8 is Unicode scalar-value order, so lexicographic comparison is a plain str::cmp). Rendered quoted / re-escaped.

§

List(Vector<Value>)

A runtime list of any arity, backed by an immutable rpds::Vector (cheap structural-sharing clone / append, O(log n) random access). The empty list is unit — there is no separate Unit value.

§

Closure

A closure capturing its defining environment over shared code. id is a per-runtime unique identity minted when the closure is built (an &-abstraction evaluating, or a partial application producing a new closure), so closures compare by identity (two separately-created &x x are distinct, and f x twice yields two unequal partials) rather than by structure — the shared code Rc is invisible to __eq. code is the abstraction’s Lambda (parameter patterns + body); applied counts the parameters already bound into env, so the remaining ones are code.head[applied..]. A partial application is one Rc::clone(code) plus a bumped applied and an extended env — no per-partial Lambda allocation. Applying the closure matches the next arguments against those remaining patterns (see apply_n).

Fields

§id: u64
§code: Rc<Lambda>
§applied: u8
§env: Env
§

Map(HashTrieMap<Value, Value>)

An immutable, persistent, unordered map from keys to values, backed by rpds::HashTrieMap (cheap structural-sharing with; unspecified iteration order, stable within a build). Any value is a key; keys are identified by structural value equality (see impl PartialEq / impl Hash). See the maps section of docs/elly-spec.md.

§

Builtin

A partially-applied builtin: a native __… operation plus the arguments gathered so far. Invoked once args.len() reaches the op’s arity.

Fields

§args: Vec<Value>
§

HostFn

A partially-applied host function: a callback the embedder supplied (HostFn) plus the arguments gathered so far, shaped exactly like Builtin and fired the same way — so a host callback curries, over-applies and partially applies like everything else, with no application path of its own.

This is the whole FFI boundary in the calling direction, and it is a value rather than an Expr variant deliberately: a host function carries its own code, so it stays meaningful wherever it is handed. The alternative — an index into a table on the Ctx — would misdispatch as soon as a value outlived the context that made it, which a host building a fresh Ctx per call does by design (see Ctx::with_ids).

Identity is the Rc pointer plus the applied arguments, matching Builtin’s “same operation, equal arguments”. The name Value::HostObj is deliberately left unused, for opaque host handles.

The applied prefix is a Box<[Value]> rather than the Vec the builtin arm carries, for width: an Rc plus a Vec is four payload words and would take Value from four words to five, which the size goldens pin (tests/sizes.rs) and which docs/done/2026-08-13_elly-modules-env.md measured the cost of. It is not a slower shape — the builtin path clones its Vec on every partial application, and Vec::clone allocates capacity == len, so both are one allocation per curried step.

Fields

§code: Rc<HostFn>
§args: Box<[Value]>
§

Module(Rc<EnvNode>)

A module instance: the module’s own Module terminal — the environment its item bodies run in. The value is the terminal rather than holding a ModuleData beside one, so a module’s code and the environment it was instantiated against cannot be recombined, and materializing an item reuses this node instead of allocating a fresh one. One payload word, so Value stays four words. Applied to a symbol it materializes that item (see apply1/apply_n). Identity is the Rc pointer (like a closure’s identity), not the item contents — two separate loads compare unequal; a content-addressed brand is deferred (see docs/done/2026-08-13_elly-modules-env.md).

§

Iota

An iota: an atomic identity a module declared with const, equal to itself and to nothing else. It has no structure and no payload, so there is nothing to read out of it and nothing outside the module can conjure one — reaching it means reaching the module.

Identity is the pair (instance, position): home is the instance’s Module terminal — the very node a Value::Module holds — and index the iota’s position in ModuleData::iota_names. Nothing is minted and no id is reserved; the value is derived whenever the declaration is referenced, so two references to one iota of one instance are equal while two instantiations of the same module mint disjoint sets. See docs/done/2026-08-13_elly-modules-iotas.md.

Fields

§home: Rc<EnvNode>
§index: u32
§

Object(Rc<ObjectData>)

An object: a payload plus the prototype module that gives it its type (see ObjectData). Built only by the __new leaf of the module that owns the type, so a value of a type is minted by that type’s own code and nowhere else.

Applied to a symbol it dispatches: the name is materialized out of the prototype and applied to the object itself, so x.m a is the module’s m with the receiver already bound (see apply1/apply_n). Compared by contents over the payload and by identity over the prototype, so objects are usable as map keys.

Implementations§

Source§

impl Value

Source

pub fn from_bigint(n: BigInt) -> Value

Build an integer value from a BigInt, demoting to the inline Value::I64 form whenever it fits. This is the single choke point that maintains the canonical invariant — an integer is Value::BigInt iff it does not fit i64 — on which PartialEq, Hash, and ordering all rely, so every wide arithmetic result must pass through here. Also the constructor hosts (the Python bindings) use to admit an arbitrary BigInt.

Source

pub fn from_bigint_ref(n: &BigInt) -> Value

from_bigint for a BigInt the caller holds by reference — an Expr::Int literal, which the AST owns and eval must not consume. The i64 demotion is decided before any clone, so a literal that fits costs no allocation at all; only a genuinely wide one is copied. Cloning first and demoting after allocated a digit vector per literal evaluation and dropped it immediately, on a path every arithmetic loop runs.

Source

pub fn host_fn( name: impl Into<Text>, arity: usize, f: impl Fn(&[Value], &Ctx) -> Result<Value, Raised> + 'static, ) -> Value

A host function as a value, ready to be applied or written into a module’s frame: the constructor a host reaches for instead of building the HostFn variant by hand. Unapplied, so the first argument starts the ordinary currying.

Panics on arity == 0 (see HostFn).

Source

pub fn list(elems: impl IntoIterator<Item = Value>) -> Value

A list value from its elements. The backing rpds::Vector is an implementation detail a host should not have to name: matching its version and features against the core’s is a real hazard (workspace feature unification is global), so building one goes through here.

Source

pub fn map(entries: impl IntoIterator<Item = (Value, Value)>) -> Value

A map value from its entries, later entries winning on an equal key. Here for the reason list is.

Source

pub fn list_items(&self) -> Option<impl ExactSizeIterator<Item = &Value>>

The elements of a list value, or None for anything else — the reading counterpart of list, so a host can take a list apart without naming rpds either. The iterator knows its length, which is usually the first thing a caller checks.

Source

pub fn remaining_arity(&self) -> Option<usize>

How many more arguments a function-shaped value wants before it runs: a closure’s unbound parameters, or a builtin’s or host function’s ungathered ones. None for everything else, including the values that are applicable without being functions — a list and a module both take a symbol, but that is projection, not a call with arguments outstanding.

A runner uses it to notice that it under-applied: __main = &(io, x) … applied to one capability yields a partial, and the program quietly does nothing unless someone says so (see docs/done/2026-08-14_elly-run.md).

Source

pub fn module_data(&self) -> Option<&Rc<ModuleData>>

The compiled module behind a Value::Module, or None for anything else. A module value holds its Module terminal rather than the ModuleData directly, so a host that only wants the item table (the Python bindings’ Obj.keys / in) reaches it through here instead of matching on the environment’s shape.

Source

pub fn object_data(&self) -> Option<&Rc<ObjectData>>

The ObjectData behind a Value::Object, or None for anything else — the payload and the prototype, for a host that wants to look inside one (the Python bindings’ Object.data / .proto).

Source

pub fn to_display(&self) -> String

A human-readable rendering used by the eval golden tests.

Trait Implementations§

Source§

impl Clone for Value

Source§

fn clone(&self) -> Value

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Value

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Hash for Value

A Hash consistent with the structural PartialEq above — required because a Value is a HashTrieMap key. A per-variant discriminant is mixed in first, so the symbol .0, the integer 0, and the string "0" cannot collide by construction. A Map key hashes order-independently (each entry folded through a fresh sub-hasher, the results summed) so two equal maps built in different insertion orders — or with hash-colliding keys — still hash equal.

Source§

fn hash<H: Hasher>(&self, state: &mut H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for Value

Structural value equality, eliminated by __eq (see the equality section of docs/elly-spec.md). Data compares structurally (integers mathematically, symbols/strings by text, lists elementwise, maps by content regardless of build order); callables compare by identity — builtins by operation then applied arguments, closures by their unique id. Values of different kinds are never equal. There is no total order over all values: the < / > ordering patterns compare only within Int / Str (see order_match).

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Eq for Value

Auto Trait Implementations§

§

impl Freeze for Value

§

impl !RefUnwindSafe for Value

§

impl !Send for Value

§

impl !Sync for Value

§

impl Unpin for Value

§

impl UnsafeUnpin for Value

§

impl !UnwindSafe for Value

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.