Closures
Closures are functions that capture variables from the surrounding scope. In Zeus, any function defined inside another function — whether named or anonymous — is a closure.
Anonymous Functions
An anonymous function is a function without a name. You can assign it to a variable or pass it directly as an argument.
let greet: () => i32 = (): i32 => { return 42;};
let result: i32 = greet(); // 42Fat Arrow Syntax
The => syntax is the standard way to write anonymous functions in Zeus:
// No parameterslet getValue: () => i32 = (): i32 => { return 10; };
// One parameterlet double: (x: i32) => i32 = (x: i32): i32 => { return x * 2; };
// Two parameterslet add: (a: i32, b: i32) => i32 = (a: i32, b: i32): i32 => { return a + b; };Named Function Expressions
You can also use the function keyword to name a closure. The name is local to the enclosing function:
function makeAdder(n: i32): (x: i32) => i32 { function add(x: i32): i32 { return x + n; } return add;}Capturing Variables
Closures capture variables from their enclosing scope by reference. This means a closure always sees the current value of the captured variable, not a snapshot taken at the time of closure creation.
Reading Captured Variables
function makeGreeter(name: i32): () => i32 { return (): i32 => { return name; };}
function main(): i32 { let sayHello: () => i32 = makeGreeter(42); return sayHello(); // 42}Mutating Captured Variables
A closure can mutate captured variables, and the change is visible to the outer scope:
function main(): i32 { let x: i32 = 0;
function increment(): i32 { x += 1; return x; }
increment(); increment();
return x; // 2}The reverse is also true — changes made in the outer scope after the closure is created are visible inside the closure:
function main(): i32 { let x: i32 = 1; let get: () => i32 = (): i32 => { return x; };
x = 99;
return get(); // 99, not 1}Common Patterns
Factory Functions (Counter)
The most common closure pattern — each call to the factory returns an independent closure with its own captured state:
function makeCounter(): () => i32 { let count: i32 = 0; return (): i32 => { count += 1; return count; };}
function main(): i32 { let c1: () => i32 = makeCounter(); let c2: () => i32 = makeCounter();
c1(); // 1 c1(); // 2 c1(); // 3 c2(); // 1 — independent from c1
if (c1() == 4 && c2() == 2) { return 0; } return 1;}Higher-Order Functions (Callbacks)
Pass closures as arguments to functions that accept function-type parameters:
function applyTwice(f: (x: i32) => i32, n: i32): i32 { return f(f(n));}
function main(): i32 { let addFive: (x: i32) => i32 = makeAdder(5); return applyTwice(addFive, 10); // 20}Adder / Multiplier Factories
function makeAdder(x: i32): (y: i32) => i32 { return (y: i32): i32 => { return x + y; };}
function makeMultiplier(factor: i32): (x: i32) => i32 { return (x: i32): i32 => { return x * factor; };}
function main(): i32 { let add3: (y: i32) => i32 = makeAdder(3); let times2: (x: i32) => i32 = makeMultiplier(2);
if (add3(7) == 10 && times2(6) == 12) { return 0; } return 1;}Capturing Class Fields
Closures can capture this and local variables from class methods:
class Scaler { public factor: i32; constructor() { this.factor = 3; }
public makeScaler(): (x: i32) => i32 { let f: i32 = this.factor; return (x: i32): i32 => { return x * f; }; }}
function main(): i32 { let s: Scaler = new Scaler(); let scale: (x: i32) => i32 = s.makeScaler(); return scale(7); // 21}Capturing Object References
When a closure captures a class instance, both the closure and the outer scope operate on the same object. Mutations to the object’s fields are shared:
class Box { public val: i32; constructor() { this.val = 0; }}
function makeBoxMutator(): () => i32 { let box: Box = new Box(); box.val = 5; return (): i32 => { box.val += 1; return box.val; };}
function main(): i32 { let mutate: () => i32 = makeBoxMutator(); mutate(); // 6 return mutate(); // 7}Reference Semantics
Zeus closures use capture by reference, not capture by value. All closures that capture the same variable share the same binding — mutations from any closure (or from the outer scope) are immediately visible everywhere.
function main(): i32 { let x: i32 = 0;
// Two closures share the same 'x' let inc: () => i32 = (): i32 => { x += 1; return x; }; let get: () => i32 = (): i32 => { return x; };
inc(); inc();
// get() reflects the mutations made by inc() if (get() == 2) { return 0; } return 1;}Type Annotations for Function Variables
Zeus uses the fat-arrow form for function type annotations:
| Signature | Type annotation |
|---|---|
(): i32 | () => i32 |
(x: i32): i32 | (x: i32) => i32 |
(a: i32, b: i32): boolean | (a: i32, b: i32) => boolean |
let counter: () => i32 = makeCounter();let adder: (x: i32) => i32 = makeAdder(5);let check: (a: i32, b: i32) => boolean = (a: i32, b: i32): boolean => { return a > b; };Nested Closures
Closures can be nested to any depth; each level captures from its own enclosing scope:
function outer(a: i32): () => () => i32 { let b: i32 = a + 10; return (): () => i32 => { return (): i32 => { return a + b; }; };}
function main(): i32 { let mid: () => () => i32 = outer(5); let inner: () => i32 = mid(); return inner(); // a=5, b=15 → 20}