Skip to content

LIMITS-005: Closure mutation blocked for outer let — leverage ceiling for stateful recursion #177

Description

@Masterplanner25

Summary

Nodus closures cannot assign to variables captured from an enclosing let scope:

let count = 0i
let inc = fn() {
    count = count + 1i  // ERROR: cannot assign to captured variable
}

The documented workaround is a quoted-key map:

let state = {"count": 0i}
let inc = fn() {
    state["count"] = state["count"] + 1i  // works
}

This workaround is functional but adds significant cognitive overhead for any program that accumulates state across recursive calls — schema validators, tree traversals, parser state machines.

Impact

This is the primary leverage ceiling for stateful data processing programs. The audit identifies "a stateful recursive data processor" as the program type hardest to write in Nodus despite being within its stated scope.

Why the restriction exists

Nodus closures capture variables by value at closure creation time. Assignment to a captured variable in Python's closure model requires nonlocal; Nodus has no equivalent keyword. The map workaround works because maps are mutable objects captured by reference.

Fix direction (v5)

Two options:

  1. Add a mut keyword to let that signals the variable is assignable from inner closures: let mut count = 0i
  2. Allow outer let assignment via upvalue mutation — detect at compile time that a variable is being assigned from an inner scope and emit a SET_UPVALUE opcode instead of SET_LOCAL

Option 2 is lower surface area; option 1 is more explicit. Either would make the closure mutation workaround unnecessary for the common case.

Affected versions

v4.0.0 (current). Noted in System Limits Audit §5 (Leverage Boundary).

Activity

  1. added this to the v5.0 milestone on Jun 8, 2026
  2. Masterplanner25 commented on Aug 17, 2026

    @Masterplanner25
    OwnerAuthor

    Triage 2026-08-17 (against v5.0.2). Duplicate of #156 — same defect (a closure cannot assign to a captured
    outer let), filed twice in the same 2026-06-07 batch under different headings
    (DESIGN-006 and LIMITS-005).

    Still reproduces on 5.0.2; see the verification posted on #156. Suggest closing one
    of the two so the backlog does not carry the same fix twice.

  3. Masterplanner25 commented on Aug 17, 2026

    @Masterplanner25
    OwnerAuthor

    Closing as a duplicate of #156.

    Both were filed in the same 2026-06-07 audit batch and describe the identical defect — a closure cannot assign to a captured outer let — under two headings (DESIGN-006 here as LIMITS-005, and DESIGN-006 there).

    Verified on 5.0.2 that it still reproduces, with the exact error #156 quotes:

    $ nodus run c1.nd
    Type error at c1.nd:2:34: Cannot add nil and int
    

    Tracking continues on #156, which carries the fuller reproduction. Nothing is lost by closing this one; the backlog just stops carrying the same fix twice.

  4. added a commit that references this issue on Aug 18, 2026
    b100824
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions