Skip to main content

EnvNode

Enum EnvNode 

Source
pub enum EnvNode {
    Cons {
        val: Value,
        next: Env,
    },
    Module {
        data: Rc<ModuleData>,
        id_base: u64,
        frame: Env,
    },
    Frame {
        vals: Box<[Value]>,
        home: Env,
    },
}
Expand description

One link of the environment list: a single binding carrying its val inline, or the frame that terminates the walk. The empty environment is None, not a node — Env is an Option<Rc<EnvNode>>, which the null-pointer niche keeps one word wide, so ending a chain costs no allocation and no refcount traffic. A lambda activation conses one Cons per name its head binds — zero for a binder-less head (&_, &(< 2)) — so a partial application’s environment is just the consed prefix, shared with later partials by Rc refcount (no copy-on-write). Names are not stored: references were resolved to a de Bruijn index against this list’s shape.

Variants§

§

Cons

Fields

§val: Value
§next: Env
§

Module

The home terminal of a module item’s environment: it carries the item’s module in place of the empty terminal, so a Expr::ModItem sibling reference resolves by walking out to it (see the ModItem arm of eval_env). It binds no name and is not counted by de Bruijn Local indices — a valid index always lands on a Cons or a Frame before reaching here. A materialized closure captures an environment ending in this node, which is how it keeps its module alive (an Rc edge closure → module, with no edge back — see docs/done/2026-08-07_elly-modules-v0.md).

This node is the module instance: Value::Module is a handle on it, minted by instantiate. So the value and the environment its items run in are one allocation, and M.name from outside runs the body in exactly the environment a sibling reference would.

id_base is the module’s reserved id block: item i stamps the closure it materializes to with id_base + i, so M.f == M.f (see materialize_body).

frame is the environment the module was instantiated against — its imports, host prelude and module-minted identities, in the slot order ModuleData::frame_names fixed at compile time. A de Bruijn index walks through this node into frame without counting it, so a frame reference is an ordinary Expr::Local and needs no variant of its own. The node binds no name itself, which is what makes passing the walk on consistent rather than an off-by-one.

The shape of frame is deliberately left open: it is an Env, so a flat Frame (named imports — a fixed slot list) and a cons chain (recur — the frame is the enclosing lexical environment) are both reachable through one code path. None is a module with no frame, which is every module compiled by crate::compile_module.

Holding an environment is what gives up modules v0’s structural acyclicity — a ModuleData could hold no Value, so no cycle was expressible — for a temporal one: a frame holds only values that existed before the frame did, and immutability means none of them can later be made to point back at it. See docs/done/2026-08-13_elly-modules-env.md.

Fields

§id_base: u64
§frame: Env
§

Frame

A closure’s captured frame: the values of the free variables its body actually uses, copied in at closure creation in the slot order Lambda::captures fixed at resolve. It terminates the de Bruijn walk: an index that runs off the consed cells selects vals[remaining] (see lookup), so a reference into an enclosing scope costs a walk over the activation’s own bindings plus one array index — never a walk over the whole enclosing program — and a closure retains only what it uses.

home carries the environment’s terminal (None, or the Module node of the module item this closure came from) on past the frame, so a ModItem inside the body still finds its module — and finds the same node, so materializing an item allocates no environment. Nothing addresses home by index: the frame is the last indexable node.

The values live inline behind the environment’s own Rc, so a frame is one allocation and is shared by refcount exactly as the captured cons tail used to be.

Fields

§vals: Box<[Value]>
§home: Env

Trait Implementations§

Source§

impl Debug for EnvNode

Source§

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

Formats the value using the given formatter. Read more

Auto Trait Implementations§

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> 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, 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.