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();
Copyright (c) 2025 Ocean Softworks, Sharkk