Labs ICT
โญ Pro Login

Closures

Anonymous functions with captured environment.

Closures in Rust

Closures are anonymous functions that can capture variables from their environment. They're more flexible than regular functions because they can remember and use values from where they were created.

The syntax is simple: vertical bars for parameters, then an expression or block.

fn main() {
    let greet = || println!("Hello!");
    greet();

    let add = |a, b| a + b;
    println!("5 + 3 = {}", add(5, 3));

    let name = "World";
    let say_hello = || println!("Hello, {}!", name);
    say_hello();
}
Try it Yourself ->

Capturing Environment

Closures can capture variables from their surrounding scope. This is what makes them so powerful and different from regular functions.

fn main() {
    let threshold = 10;
    let check = |value| value > threshold;

    println!("15 > 10: {}", check(15));
    println!("5 > 10: {}", check(5));

    let mut count = 0;
    let mut increment = || {
        count += 1;
        count
    };

    println!("Count: {}", increment());
    println!("Count: {}", increment());
    println!("Count: {}", increment());
}
Try it Yourself ->

Fn, FnMut, and FnOnce Traits

Closures implement one of three traits based on how they capture variables: Fn (immutable), FnMut (mutable), or FnOnce (consuming).

fn call_once<F: FnOnce()>(f: F) {
    f();
}

fn call_mut<F: FnMut()>(mut f: F) {
    f();
    f();
}

fn call_ref<F: Fn()>(f: F) {
    f();
    f();
    f();
}

fn main() {
    let name = String::from("Alice");

    // FnOnce: consumes the captured value
    let consume = || println!("Consumed: {}", name);
    call_once(consume);

    // FnMut: modifies captured values
    let mut count = 0;
    let mut mutate = || count += 1;
    call_mut(&mut mutate);
    println!("Count: {}", count);

    // Fn: only reads captured values
    let greeting = String::from("Hello");
    let read = || println!("Read: {}", greeting);
    call_ref(read);
}
Try it Yourself ->

Move Closures

The move keyword forces a closure to take ownership of captured variables. This is useful when you need the closure to live longer than the original scope.

fn create_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

fn main() {
    let add5 = create_adder(5);
    let add10 = create_adder(10);

    println!("5 + 3 = {}", add5(3));
    println!("10 + 7 = {}", add10(7));

    let data = vec![1, 2, 3];
    let print_data = move || println!("Data: {:?}", data);
    print_data();
    // data is moved, can't use it here
}
Try it Yourself ->

Closures in Iterators

Closures are perfect for iterator methods like map, filter, and fold. They let you transform and process collections elegantly.

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let evens: Vec<_> = numbers.iter()
        .filter(|&&x| x % 2 == 0)
        .cloned()
        .collect();

    let squared: Vec<_> = numbers.iter()
        .map(|&x| x * x)
        .collect();

    let sum = numbers.iter()
        .fold(0, |acc, &x| acc + x);

    let product = numbers.iter()
        .take(5)
        .fold(1, |acc, &x| acc * x);

    println!("Evens: {:?}", evens);
    println!("Squared: {:?}", squared);
    println!("Sum: {}", sum);
    println!("Product of first 5: {}", product);
}
Try it Yourself ->

Why Closures Are Powerful

Closures are more powerful than function pointers because they can capture and remember their environment. This makes them perfect for callbacks, iterators, and functional programming patterns.

In Rust, closures are zero-cost abstractions. The compiler optimizes them just like regular functions, so you get the flexibility without performance penalties.

๐Ÿงช Quick Quiz

What trait do closures implement?