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; };}
let sayHello: () => i32 = makeGreeter(42);console.log(sayHello()); // 42Mutating Captured Variables
A closure can mutate captured variables, and the change is visible to the outer scope:
function bumpTwice(): i32 { let x: i32 = 0;
function increment(): i32 { x += 1; return x; }
increment(); increment();
return x; // 2}
console.log(bumpTwice()); // 2The reverse is also true — changes made in the outer scope after the closure is created are visible inside the closure:
function readLatest(): i32 { let x: i32 = 1; let get: () => i32 = (): i32 => { return x; };
x = 99;
return get(); // 99, not 1}
console.log(readLatest()); // 99Common 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; };}
let c1: () => i32 = makeCounter();let c2: () => i32 = makeCounter();
console.log(c1()); // 1console.log(c1()); // 2console.log(c1()); // 3console.log(c2()); // 1 — independent from c1console.log(c1()); // 4console.log(c2()); // 2Higher-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));}
let addFive: (x: i32) => i32 = makeAdder(5);console.log(applyTwice(addFive, 10)); // 20Adder / 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; };}
let add3: (y: i32) => i32 = makeAdder(3);let times2: (x: i32) => i32 = makeMultiplier(2);
console.log(add3(7)); // 10console.log(times2(6)); // 12Capturing 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; }; }}
let s: Scaler = new Scaler();let scale: (x: i32) => i32 = s.makeScaler();console.log(scale(7)); // 21Capturing 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; };}
let mutate: () => i32 = makeBoxMutator();console.log(mutate()); // 6console.log(mutate()); // 7Reference 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 sharedBinding(): 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() return get();}
console.log(sharedBinding()); // 2Type 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; }; };}
let mid: () => () => i32 = outer(5);let inner: () => i32 = mid();console.log(inner()); // a=5, b=15 → 20