Closures
Closures are functions that can capture values from their environment. They're essential for functional programming patterns—map, filter, callbacks, and more.
Basic Syntax
// Closure that adds one
$add_one = ($x) { $x + 1 };
echo $add_one(5); // 6
The syntax is ($params) { body }. Type annotations are usually inferred:
// Explicit types if needed
$add = ($a i32, $b i32): i32 { $a + $b };
Capturing Variables
Closures can use variables from their surrounding scope:
$multiplier = 3;
$triple = ($x) { $x * $multiplier };
echo $triple(4); // 12
The closure captures $multiplier automatically.
Capture Modes
Closures capture in the least restrictive way needed:
By Reference (default)
$counter = 0;
$increment = () {
$counter += 1; // Captures $counter by mutable reference
};
$increment();
$increment();
echo $counter; // 2
By Value (move)
Use move when you need the closure to own the data:
fn make_greeter($name string): fn() {
// $name would be invalid after this function returns
// so we move it into the closure
return move || {
echo "Hello, {$name}!";
};
}
$greet = make_greeter("Alice");
$greet(); // Hello, Alice!
The move keyword transfers ownership of captured variables into the closure.
Using Closures
Closures shine with iterator methods:
$numbers = [1, 2, 3, 4, 5];
// Map
$doubled = $numbers.iter().map(($x) { $x * 2 }).collect();
// Filter
$evens = $numbers.iter().filter(($x) { $x % 2 == 0 }).collect();
// Fold
$sum = $numbers.iter().fold(0, ($acc, $x) { $acc + $x });
Higher-Order Functions
Functions that take or return functions:
// Takes a function
fn apply_twice<T>($f fn(T): T, $x T): T {
return $f($f($x));
}
$result = apply_twice(($x) { $x * 2 }, 3);
echo $result; // 12
// Returns a function
fn make_adder($n i32): fn(i32): i32 {
return ($x) { $x + $n };
}
$add_five = make_adder(5);
echo $add_five(10); // 15
Function Types
Closure types are inferred, but you can specify them:
// Function that takes an i32 and returns an i32
fn(i32): i32
// Function that takes nothing and returns nothing
fn()
// Function that takes two strings and returns a bool
fn(string, string): bool
Closure Traits
Closures implement one of three traits based on how they use captured variables:
Fn
Can be called multiple times, captures by reference:
fn call_twice<F: Fn()>($f F) {
$f();
$f();
}
$message = "Hello";
call_twice(|| { echo $message }); // Works
FnMut
Can be called multiple times, captures by mutable reference:
fn call_and_count<F: FnMut()>($f &F, $times i32) {
for _ in 0..$times {
$f();
}
}
$count = 0;
call_and_count(&|| { $count += 1 }, 5);
echo $count; // 5
FnOnce
Can only be called once, takes ownership of captures:
fn consume<F: FnOnce() -> String>($f F): String {
return $f(); // Consumes the closure
}
$data = "important".to_string();
$result = consume(move || { $data }); // $data is moved
Common Patterns
Callbacks
fn fetch_async($url string, $callback fn(Response)) {
// ... start async operation ...
// When done, call callback
$callback($response);
}
fetch_async("https://api.example.com", ($response) {
echo "Got: {$response.status}";
});
Event Handlers
$button.on_click(($event) {
echo "Button clicked at ({$event.x}, {$event.y})";
});
Sorting with Custom Order
$users.sort_by(($a, $b) {
$a.name.cmp(&$b.name)
});
Lazy Initialization
$expensive = Lazy::new(|| {
echo "Computing...";
heavy_computation()
});
// Computation happens on first access
echo $expensive.get();
Guards and Cleanup
fn with_file<F: FnOnce(&File)>($path string, $f F): !() {
$file = File::open($path);
$f(&$file);
// File automatically closed here
}
with_file("data.txt", ($file) {
$content = $file.read_all();
process($content);
});
Closures vs Named Functions
When to use which?
Use closures:
- Short, one-off functions
- When you need to capture environment
- With iterator methods
- As callbacks
Use named functions:
- Reusable logic
- Complex implementations
- When you need documentation
- Public API
// Closure - good for inline use
$doubled = $numbers.iter().map(($x) { $x * 2 }).collect();
// Named function - good for reuse and documentation
fn double($x i32): i32 {
return $x * 2;
}
$doubled = $numbers.iter().map(double).collect();
Performance
Closures are zero-cost abstractions. They compile down to efficient code:
- Non-capturing closures are just function pointers
- Capturing closures are inlined when possible
- No heap allocation unless you box them
// This is as fast as a hand-written loop
$sum = $numbers.iter().filter(($x) { $x > 0 }).sum();
Best Practices
Keep closures short. If a closure is getting long, extract it into a named function.
Be explicit about move. When closures outlive their scope, use move to be clear about ownership.
Prefer Fn over FnMut over FnOnce. Use the least restrictive trait that works.
Name complex closures. For readability:
$is_valid_user = ($u) {
$u.age >= 18 && $u.verified && !$u.banned
};
$valid_users = $users.iter().filter($is_valid_user).collect();