Skip to content

Non-local variable

Treat a variable reference as non-local to a program unit when its binding is supplied by an enclosing or otherwise noncurrent scope, making scope resolution and captured-environment lifetime explicit.

Version
v2 · 2026-08-30 · History
Domain-specific #
2384
Origin domain
programming language semantics
Subdomain
scope and environment representation

Core Idea

A variable occurrence is non-local relative to a program unit when its binding is not declared in that unit's local environment; in the central lexical-scope case, the declaration is found in an enclosing textual scope rather than in the global environment or caller-selected environment.[1] Name resolution walks the language's scope relation from the current environment toward enclosing environments until it finds the governing declaration, respecting shadowing. At runtime, nested procedures may reach the binding through static links, a display, or a closure environment; if the procedure escapes, captured storage must outlive the activation that originally created it.

Its autonomous residual is the reference-to-outer-binding relation viewed from a particular unit, not the variable's intrinsic type, a global variable by definition, or the complete closure value that may capture it. The identity fails when locality is asserted without naming the reference unit, textual nesting is confused with call-stack ancestry, a same-name local declaration is ignored, global and enclosing non-global cases are conflated, or an escaped binding is assumed to remain on a dead stack frame.

Recognition requires an analyst to mark the reference and every same-name declaration, apply the specified lexical or dynamic rule, identify shadowing, distinguish module-global from enclosing-function bindings, trace runtime environment access, and test whether an escaping function preserves the required storage. Once established, it supports explaining lexical capture, compiling nested procedures, designing environment representations, analyzing mutation shared across closures, diagnosing accidental capture, and separating static name binding from dynamic activation records without turning those uses into the definition.

Structural Signature

  • Carrier: a program unit or expression, a variable occurrence within it, a hierarchy or chain of binding environments, and a language-specific scope rule
  • Inputs or antecedent state: the reference site, candidate declarations, lexical or dynamic scope convention, shadowing rules, environment links or display entries, mutation semantics, and lifetime or closure conversion
  • Constitutive operation: Name resolution walks the language's scope relation from the current environment toward enclosing environments until it finds the governing declaration, respecting shadowing. At runtime, nested procedures may reach the binding through static links, a display, or a closure environment; if the procedure escapes, captured storage must outlive the activation that originally created it.
  • Invariant: locality is stated relative to one referencing unit, a governing declaration lies outside its local scope, the language's resolution rule selects that declaration, and runtime representation preserves the selected binding for as long as the reference can be evaluated
  • Recognition test: mark the reference and every same-name declaration, apply the specified lexical or dynamic rule, identify shadowing, distinguish module-global from enclosing-function bindings, trace runtime environment access, and test whether an escaping function preserves the required storage
  • Output or consequence: explaining lexical capture, compiling nested procedures, designing environment representations, analyzing mutation shared across closures, diagnosing accidental capture, and separating static name binding from dynamic activation records
  • Failure boundary: locality is asserted without naming the reference unit, textual nesting is confused with call-stack ancestry, a same-name local declaration is ignored, global and enclosing non-global cases are conflated, or an escaped binding is assumed to remain on a dead stack frame

What It Is Not

  • It is not the whole field of programming language semantics; many objects in that field do not satisfy its constitutive rule.
  • It is not its canonical example. In a lexically scoped language, an inner function that increments x declared in its enclosing outer function contains a non-local reference to that particular x. That is an instance, not a definition.
  • It is not Closure (programming). A closure is a runtime function value paired with bindings for free variables. A non-local variable is one binding relation observed from a nested unit; it can be implemented by a static chain without creating a first-class escaping closure, and a closure may capture several such bindings.
  • It is not an unrestricted metaphor. Non-local is sometimes used broadly enough to include globals, while Python's nonlocal declaration specifically targets bindings in enclosing function scopes and excludes the global scope; the entry records the language convention rather than universalizing one keyword

Scope of Application

Non-local variable applies when the analyst can specify a program unit or expression, a variable occurrence within it, a hierarchy or chain of binding environments, and a language-specific scope rule and establish that locality is stated relative to one referencing unit, a governing declaration lies outside its local scope, the language's resolution rule selects that declaration, and runtime representation preserves the selected binding for as long as the reference can be evaluated. The entry describes programming-language binding semantics and implementation consequences, not a style recommendation or a claim that shared mutable non-local state is always desirable.[2]

  • Recognition. mark the reference and every same-name declaration, apply the specified lexical or dynamic rule, identify shadowing, distinguish module-global from enclosing-function bindings, trace runtime environment access, and test whether an escaping function preserves the required storage
  • Comparison. Compare legitimate instances through reference unit, lexical versus dynamic scope, declaration depth, shadowing, global treatment, mutability, capture set, environment representation, escape behavior, lifetime, and concurrency.
  • Boundary. Non-local is sometimes used broadly enough to include globals, while Python's nonlocal declaration specifically targets bindings in enclosing function scopes and excludes the global scope; the entry records the language convention rather than universalizing one keyword
  • Use. Preserve every assumption when using the identity for explaining lexical capture, compiling nested procedures, designing environment representations, analyzing mutation shared across closures, diagnosing accidental capture, and separating static name binding from dynamic activation records.

Clarity

A clear claim names the carrier, governing rule, assumptions, and recognition test. This matters because the term is relative to a particular program unit and different languages either include or exclude global variables, so examples must name both the reference point and binding rule. The disciplined statement is that the object counts as Non-local variable exactly when locality is stated relative to one referencing unit, a governing declaration lies outside its local scope, the language's resolution rule selects that declaration, and runtime representation preserves the selected binding for as long as the reference can be evaluated

Identity and measurement remain separate. Correctness is established through scope-resolution and lifetime semantics; performance comparisons must distinguish static-link traversal, displays, environment objects, and compiler optimizations. Approximation or noisy evidence may weaken a classification without changing its definition.

Manages Complexity

The abstraction compresses lexically scoped nested functions, dynamically scoped languages, module globals, Python nonlocal declarations, Lua upvalues, static-chain and display implementations, closure conversion, and capture-by-value or by-reference policies into a stable carrier, rule, invariant, and failure boundary. It makes comparison tractable while retaining the variables that control validity.

Compression can hide assumptions. A responsible use therefore declares reference unit, lexical versus dynamic scope, declaration depth, shadowing, global treatment, mutability, capture set, environment representation, escape behavior, lifetime, and concurrency and returns to the full diagnostic whenever a convention or boundary case changes.

Abstract Reasoning

  1. Type the carrier. Establish a program unit or expression, a variable occurrence within it, a hierarchy or chain of binding environments, and a language-specific scope rule and reject examples from a different problem.
  2. Lock the rule. Express that locality is stated relative to one referencing unit, a governing declaration lies outside its local scope, the language's resolution rule selects that declaration, and runtime representation preserves the selected binding for as long as the reference can be evaluated independently of one notation or implementation.
  3. Derive carefully. Infer explaining lexical capture, compiling nested procedures, designing environment representations, analyzing mutation shared across closures, diagnosing accidental capture, and separating static name binding from dynamic activation records only under the stated assumptions.
  4. Stress-test. Contrast the legitimate boundary case—Non-local is sometimes used broadly enough to include globals, while Python's nonlocal declaration specifically targets bindings in enclosing function scopes and excludes the global scope; the entry records the language convention rather than universalizing one keyword—with this counterexample: a function parameter is visible inside the function but is local there, so using it does not become a non-local reference merely because the argument value originated at the caller.

Knowledge Transfer

Transfer within programming language semantics is strong when new cases preserve the same carrier, mechanism, and diagnostic. The move from In a lexically scoped language, an inner function that increments x declared in its enclosing outer function contains a non-local reference to that particular x. to A compiler represents a returned nested function as code plus an environment containing or pointing to its captured bindings, promoting an escaping mutable binding from an ordinary stack slot when necessary. demonstrates that continuity.[3]

Outside the domain, only the skeleton—resolve a local demand through a dependency chain into an enclosing context and retain the supplying state while the demand remains live—travels automatically. The terms scope, environment, binding, declaration, lexical address, static link, display, free variable, capture, closure, shadowing, and lifetime retain domain-specific meanings, so every role and inference must be revalidated.

Examples

Canonical

In a lexically scoped language, an inner function that increments x declared in its enclosing outer function contains a non-local reference to that particular x. A local x inside the inner function would shadow the outer declaration; a top-level x would instead be global. The classification is therefore relative to the inner function and its resolved declaration. It is canonical because the carrier, rule, invariant, and consequence are all inspectable.[1]

Mapped back: a program unit or expression, a variable occurrence within it, a hierarchy or chain of binding environments, and a language-specific scope rule → Name resolution walks the language's scope relation from the current environment toward enclosing environments until it finds the governing declaration, respecting shadowing. At runtime, nested procedures may reach the binding through static links, a display, or a closure environment; if the procedure escapes, captured storage must outlive the activation that originally created it. → locality is stated relative to one referencing unit, a governing declaration lies outside its local scope, the language's resolution rule selects that declaration, and runtime representation preserves the selected binding for as long as the reference can be evaluated → explaining lexical capture, compiling nested procedures, designing environment representations, analyzing mutation shared across closures, diagnosing accidental capture, and separating static name binding from dynamic activation records

Applied / In Practice

A compiler represents a returned nested function as code plus an environment containing or pointing to its captured bindings, promoting an escaping mutable binding from an ordinary stack slot when necessary. Closure conversion implements access and lifetime; it does not make every variable in an enclosing scope captured, because analysis can retain only the free bindings actually referenced. It qualifies only after the same diagnostic and failure boundary are checked.[2]

Mapped back: declared instance → recognition test → boundary check → qualified use

Structural Tensions

  • T1: Exact identity vs. practical recognition. The constitutive condition may be exact while evidence is indirect. Diagnostic: Can the reviewer state both the condition and the warrant?
  • T2: Canonical form vs. variants. lexically scoped nested functions, dynamically scoped languages, module globals, Python nonlocal declarations, Lua upvalues, static-chain and display implementations, closure conversion, and capture-by-value or by-reference policies can preserve or change the identity. Diagnostic: Which named role is invariant across the variants?
  • T3: Compression vs. hidden assumptions. The label is useful only while prerequisites remain visible. Diagnostic: Can each downstream inference be traced to a declared assumption?
  • T4: Autonomy vs. reduction. The candidate uses broader structures but claims the reference-to-outer-binding relation viewed from a particular unit, not the variable's intrinsic type, a global variable by definition, or the complete closure value that may capture it. Diagnostic: Does that residual still support independent recognition after the parent and neighbors are subtracted?

Structural–Framed Character

The entry is structurally mixed but domain-framed. Its portable skeleton is resolve a local demand through a dependency chain into an enclosing context and retain the supplying state while the demand remains live; its identity-bearing terms are scope, environment, binding, declaration, lexical address, static link, display, free variable, capture, closure, shadowing, and lifetime. Those terms determine admissible objects, evidence, and consequences inside programming language semantics.

Structural Core vs. Domain Accent

The structural core is a carrier governed by Name resolution walks the language's scope relation from the current environment toward enclosing environments until it finds the governing declaration, respecting shadowing. At runtime, nested procedures may reach the binding through static links, a display, or a closure environment; if the procedure escapes, captured storage must outlive the activation that originally created it. and tested by mark the reference and every same-name declaration, apply the specified lexical or dynamic rule, identify shadowing, distinguish module-global from enclosing-function bindings, trace runtime environment access, and test whether an escaping function preserves the required storage. The domain accent is constitutive rather than decorative, so an analogy that preserves only the skeleton is not another instance of Non-local variable.

The proposed strict upward parent is prime:dependency. A non-local reference literally depends on a binding supplied outside the current local environment and fails if that binding cannot be resolved or retained; scope and runtime-environment rules supply the programming-language residual. The edge is proposal-only and points to a frozen prior-baseline Prime.

The entry does not collapse into the parent because the reference-to-outer-binding relation viewed from a particular unit, not the variable's intrinsic type, a global variable by definition, or the complete closure value that may capture it A thematic neighbor is declined whenever it does not literally subsume that rule.

The prospective workspace queue contains one strict upward edge to prime:dependency. No live DAG mutation is authorized.

Relationships to Other Abstractions

Local relationship map for Non-local variableParents appear above the current abstraction, mutual partners to the right, and children below. Node labels state whether each abstraction is prime or domain-specific; colors identify relation types.Non-local variableDOMAINPrime abstraction: Dependency — is a kind ofDependencyPRIME

Current abstraction Non-local variable Domain-specific

Parents (1) — more general patterns this builds on

  • Non-local variable is a kind of Dependency Prime

    The proposed strict upward parent is prime:dependency.

Hierarchy path (1) — routes to 1 parentless root

Neighborhood in Abstraction Space

Non-local variable sits in a moderately populated region (52nd percentile for distinctiveness): it has near-neighbors but no dense thicket of look-alikes.

Family — Syntax, Rewriting & Declarative Form (41 abstractions)

Nearest neighbors

Computed from structural-signature embeddings · 2026-09-08

Not to Be Confused With

  • Global variable. A binding in a program- or module-level environment; some usage includes it under non-local, while narrower usage distinguishes it.
  • Free variable. A syntactic occurrence not bound within an expression; its eventual binding can be supplied by a closure or larger context.
  • Closure. The runtime package preserving code and captured environment, not one variable reference.
  • Dynamic variable. Resolves through calling context or dynamic binding rules rather than lexical enclosure.

References

[1] Alfred V. Aho, Monica S. Lam, Ravi Sethi, and Jeffrey D. Ullman, Compilers: Principles, Techniques, and Tools, 2nd ed., Pearson, 2006, §7.3 'Access to Nonlocal Data on the Stack,' ISBN 978-0-321-48681-3. registry ↩a ↩b

[2] Andrew W. Appel, Modern Compiler Implementation in ML, Cambridge University Press, 1998, chapters on frames and closure conversion, DOI 10.1017/CBO9780511811449. registry ↩a ↩b

[3] Robert W. Sebesta, Concepts of Programming Languages, 11th ed., Pearson, 2016, chapters 5 and 10, ISBN 978-0-13-394302-3. registry