পাঠ ১৩.১

Closure

Closures: Anonymous Functions that Capture Their Environment

Closure = anonymous function — variable-এ রাখা যায়, argument হিসেবে pass করা যায়। Function থেকে আলাদা — closure enclosing scope থেকে variable capture করতে পারে।

Closure syntax

Pipe |...| দিয়ে parameter। Type annotation optional — Rust use দেখে infer করে।

fn  add_one_v1   (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x|             { x + 1 };
let add_one_v4 = |x|               x + 1  ;

Environment capture

T-shirt giveaway example:

#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
    Red,
    Blue,
}

struct Inventory {
    shirts: Vec<ShirtColor>,
}

impl Inventory {
    fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
        user_preference.unwrap_or_else(|| self.most_stocked())
    }

    fn most_stocked(&self) -> ShirtColor {
        let mut num_red = 0;
        let mut num_blue = 0;

        for color in &self.shirts {
            match color {
                ShirtColor::Red => num_red += 1,
                ShirtColor::Blue => num_blue += 1,
            }
        }
        if num_red > num_blue { ShirtColor::Red } else { ShirtColor::Blue }
    }
}

|| self.most_stocked() closure self-এর immutable reference capture করছে। Function এটা পারে না।

Type inference — locked after first use

fn main() {
    let example_closure = |x| x;

    let s = example_closure(String::from("hello"));
    let n = example_closure(5);  // ERROR
}

প্রথম call-এ type infer হয়ে String fixed — পরে integer দিলে error।

তিনভাবে capture

১. Immutable borrow

fn main() {
    let list = vec![1, 2, 3];
    println!("Before defining closure: {list:?}");

    let only_borrows = || println!("From closure: {list:?}");

    println!("Before calling closure: {list:?}");
    only_borrows();
    println!("After calling closure: {list:?}");
}

list pre, during, post — সবখানে accessible (just reading)।

২. Mutable borrow

fn main() {
    let mut list = vec![1, 2, 3];

    let mut borrows_mutably = || list.push(7);

    borrows_mutably();
    println!("After calling closure: {list:?}");
}

Closure-ও mut। closure-এর use period-এ list অন্য জায়গায় access করা যাবে না।

৩. Move ownership

use std::thread;

fn main() {
    let list = vec![1, 2, 3];

    thread::spawn(move || println!("From thread: {list:?}"))
        .join()
        .unwrap();
}

move keyword — closure ownership নেয়। Thread-এ পাঠানোর সময় essential — main thread আগে শেষ হয়ে গেলে reference invalid হয়ে যেত।

Fn trait family

Closure কীভাবে capture handle করছে তার উপর ভিত্তি করে স্বয়ংক্রিয়ভাবে এক বা একাধিক trait implement করে:

  • FnOnce — সবাই implement করে, কারণ অন্তত একবার call করা যায়। Captured value-কে move করে এমন closure শুধু FnOnce।
  • FnMut — captured value mutate করে, কিন্তু move করে না। একাধিকবার call করা যায়।
  • Fn — neither move nor mutate। concurrent call-ও safe।

unwrap_or_else-এর signature এই concept use করে:

impl<T> Option<T> {
    pub fn unwrap_or_else<F>(self, f: F) -> T
    where
        F: FnOnce() -> T
    {
        match self {
            Some(x) => x,
            None => f(),
        }
    }
}

FnOnce — সবচেয়ে flexible bound, কারণ একবার call হবে।

sort_by_key — FnMut লাগে

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let mut list = [
        Rectangle { width: 10, height: 1 },
        Rectangle { width: 3, height: 5 },
        Rectangle { width: 7, height: 12 },
    ];

    list.sort_by_key(|r| r.width);
    println!("{list:#?}");
}

sort_by_key closure-কে multiple বার call করে — প্রতিটা item-এর জন্য — তাই FnMut চায়।

FnOnce-এর মতো closure দিলে fail

fn main() {
    let mut list = [
        Rectangle { width: 10, height: 1 },
        Rectangle { width: 3, height: 5 },
        Rectangle { width: 7, height: 12 },
    ];

    let mut sort_operations = vec![];
    let value = String::from("closure called");

    list.sort_by_key(|r| {
        sort_operations.push(value);  // value MOVE হচ্ছে — শুধু একবার-যোগ্য
        r.width
    });
}
compile errortext
error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure

value ownership-by-move — দ্বিতীয়বার call-এ আর থাকবে না। FnOnce এ ঠিক, কিন্তু sort_by_key FnMut চায়।

Fix — counting

let mut num_sort_operations = 0;
list.sort_by_key(|r| {
    num_sort_operations += 1;
    r.width
});

এখানে closure শুধু mutable reference capture করছে (move না) — FnMut satisfied।

Closure বনাম Function — পার্থক্য

FeatureClosureFunction
Capture environment
Type annotationOptionalআবশ্যক
Inline syntax|x| x + 1fn ...

এই পাঠ থেকে যা শিখলে

  • |x| body closure syntax; type inferred।
  • Capture তিন ভাবে — immutable borrow, mutable borrow, move ownership।
  • FnOnce / FnMut / Fn — call frequency + mutation constraint।
  • Higher-order method (e.g., unwrap_or_else,sort_by_key) appropriate trait bound use করে।