Closures

A closure is a function without a name. You can write one inline, store it in a variable, or pass it to another function.

Basic syntax

A closure is wrapped in braces. Parameters come before in; the body comes after:

let double = { (x: Int) in x * 2 }; let result = double(5); // 10

When the type is known from context, you can drop the parameter type:

let nums = [1, 2, 3]; let doubled = nums.map({ (x) in x * 2 });

The it shorthand

For a single-parameter closure where the type is inferred, you can drop the parameter list entirely and refer to the argument as it:

let nums = [1, 2, 3]; nums.map { it * 2 }; nums.filter(where: { it > 0 });

This makes pipelines read like prose. Use named parameters when the body is long enough that it becomes unclear.

Trailing closures

When a closure is the last argument and that parameter is unlabeled, you can write it after the parentheses, opening the brace on the same line as the call:

func retry(times times: Int, action: () -> Bool) -> Bool { for _ in 0..<times { if action() { return true; }; }; false } func ping() -> Bool { true } retry(times: 3) { ping() };

A labeled closure parameter can't be moved out — it stays inside the parentheses, which is why filter above was written nums.filter(where: { it > 0 }).

If the closure is the function's only argument (and unlabeled), you can drop the parentheses entirely:

func compute(work: () -> Int) -> Int { work() } func expensiveOperation() -> Int { 42 } let value = compute { expensiveOperation() };

This is the syntax that makes Kestrel's for-like APIs (each, map, filter) feel built-in even though they're just functions.

Capture

Closures capture values from the surrounding scope by value — the binding is copied at the moment the closure is created:

var counter = 0; let snapshot: () -> Int = { counter }; counter = 99; let value = snapshot(); // 0, not 99

(A parameterless closure needs either a type annotation, as above, or an explicit empty parameter list — { () in counter } — otherwise a bare { counter } is read as a one-parameter it closure.) If you need a closure to see later changes, pass the current value in as a parameter instead of capturing it.

Closures as values

A closure has a function type written (Args) -> Return:

let op: (Int, Int) -> Int = { (a, b) in a + b }; func apply(f: (Int, Int) -> Int, a: Int, b: Int) -> Int { f(a, b) } apply(op, 3, 4); // 7

This is what lets functions take behavior as data — the foundation of map, filter, callback APIs, and the iterator chain.