blog / Design notes
A closure keeps a binding
A small counter reveals what a function carries out of the scope where it was created.
Consider a function that creates another function. The inner function refers to a local variable of the outer function. When the outer call returns, what should the inner function keep?
KataScript keeps access to the binding. It does not freeze a snapshot of the original value.
Start with a counter
The stateful counter tutorial uses a factory with two parameters: a starting value and a step. The factory returns a function that adds the step to the captured value and returns the new total.
Call the returned function repeatedly, and the updates persist. Call the factory a second time, and the new counter has its own binding. Two functions can have the same source while carrying different state.
That gives us two distinct relationships to check:
- Repeated calls to one counter share its state.
- Counters created by separate factory calls have separate state.
The downloadable counter program makes both visible. It interleaves a counter that increments by one with a counter that increments by ten. Their outputs progress independently.
A slot, not a copied value
The interpreter represents a captured binding with a shared slot. Both the enclosing scope and a closure can refer to that slot. Assignment updates the stored value, so a closure sees mutations made through the enclosing binding and the enclosing scope sees mutations made by the closure.
This is lexical scope: a function’s references come from where it was defined. Calling it from a different scope does not substitute the caller’s variables. The identity of the captured binding is what survives.
In the Rust implementation, the shared slot uses Arc<Mutex<Value>>. That is an implementation choice for shared mutable access, not a promise that KataScript provides concurrent execution. The language-level concept is simpler: a binding has one current value, and multiple closures can refer to it.
The lifetime question follows
Once functions can carry references to bindings, scope exit alone cannot describe every value’s lifetime. Some bindings must remain available after their creating call returns. A function can also refer back to a binding that contains a function, which makes cycles possible.
The current interpreter uses reference-counted capture slots. Cycle collection and the interaction between closures, copying, and automatic destruction remain areas that need care. The useful counter behavior is implemented; a complete resource-lifetime model is a larger problem.
A small program is valuable here because it distinguishes the language rule from those implementation questions. We can specify which updates a counter must observe, test that behavior, and then improve storage and cleanup without changing the intended result.
Read the approved closure decision, or inspect source, checked output, and AST in the counter example.