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.