Closures
Anonymous functions, the capture modes the compiler infers, and what @noescape buys you.
A closure is an anonymous function that can capture variables from the scope around it. The body is an expression or a block.
function main(): i32 {
let add = (a: i32, b: i32): i32 -> a + b;
let double = (x: i32): i32 -> x * 2;
return add(20, double(11));
}
They can be stored in variables, declared at module level, and passed as arguments.
const add: (i32, i32) -> i32 = (a: i32, b: i32): i32 -> a + b;
function apply(@noescape f: (i32) -> i32, x: i32): i32 {
return f(x);
}
function main(): i32 {
return apply((n: i32): i32 -> n * 2, 21);
}
Capture modes
The mode is inferred from how the variable is used inside the closure.
| Use inside the closure | Mode | Effect |
|---|---|---|
| Read only, copyable type | By value | A copy taken when the closure is created |
| Read only, non-copyable type | Shared reference | A pointer into the enclosing scope |
| Mutated | Mutable reference | A mutable pointer into the enclosing scope |
| Moved | By value | Ownership transferred when the closure is created |
Three builtins override the inference from inside the body: @move forces a by-value snapshot,
@ref a shared reference, @refMut a mutable one. The snapshot matters more than it looks — a
@move capture reads the value as it was at creation, not as it is at call time.
Escaping
A closure that captures by reference and then outlives the scope it captured from would dangle. The
compiler refuses that: storing such a closure in a field, returning it, or passing it to a parameter
that is not marked @noescape is an error.
@noescape on a parameter is the promise that the closure will not outlive the call, which is what
lets the capture stay a pointer instead of a heap allocation.
function forEach(data: *i32, len: i32, @noescape f: (i32) -> void): void {
let mut i: i32 = 0;
while (i < len) {
f(data[i as u64]);
i = i + 1;
}
return;
}
function main(): i32 {
let arr: i32[3] = [10, 20, 12];
let mut sum: i32 = 0;
forEach((&arr[0]) as *i32, 3, (x: i32): void -> { sum = sum + x; });
return sum;
}
Non-escaping closures keep their environment on the stack. Escaping ones get a heap-allocated environment and a drop function, which is the cost you are agreeing to when you let one escape.