Step 1 of 3 · Reading · ~3 min
Closures — Capturing the Environment
Closures & Classes
Closures — Functions That Capture State
A closure is a function bundled with the environment it was defined in. You already have almost everything you need — you just have to make sure LoxFunction remembers which environment to chain onto, rather than always starting from globals.
The environment chain
Every Environment has a reference to an enclosing environment, forming a chain up to the global scope. Variable lookup walks up this chain until it finds the name:
Capturing at definition time, not call time
The critical line is in visit_function_declaration: when you build the LoxFunction, pass in the environment active right now, at the point the fun statement executes — not the global environment, and not some environment computed later.
That captured environment is the closure. When the function is later called, LoxFunction.call creates a new environment for the parameters — but its enclosing is the closure, not whatever environment happens to be active at the call site:
This one line is the entire mechanism. It's what makes lexical (static) scoping work: a function always sees the variables that were in scope where it was written, regardless of where it's called from.
Walking through the counter example
fun counter() {
var n = 0;
fun inc() {
n = n + 1;
return n;
}
return inc;
}
var c = counter();
print c(); // 1
print c(); // 2
- Calling
counter()creates a new environment E1 (enclosing = global) and definesn = 0in it. - The nested
fun inc() { ... }declaration runs inside that call, soinc's closure is E1 — the environment holdingn. counterreturns theincfunction object (aLoxFunctionwhoseclosureis E1) and the call tocounter()ends — but E1 isn't garbage collected, becauseinc(assigned toc) still holds a reference to it.- Each call
c()creates a fresh call-environment enclosing E1, mutatesninside E1 viaassign(which walks up the chain to find it), and returns the updated value.
Because n lives in E1, not in the transient per-call environment, it persists across calls to c() — that's the closure "remembering" state.
Edge cases to watch
- Capturing by reference to the environment, not by copying the value: two closures created in the same scope share and mutate the same variable.
- Closures created inside a loop each need their own environment per iteration if the loop body declares a fresh
vareach time — otherwise all closures end up sharing the same final value (a classic bug in many real languages, not just yours).
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…