Rust: الإغلاقات في لغة Rust

آخر تحديث: 2026-08-26

الإغلاق هو دالة مجهولة يمكنها التقاط متغيرات من البيئة المحيطة بها. يتم استدعاؤها تمامًا مثل أي دالة، لكنها قادرة على «تذكر» المتغيرات الموجودة في النطاق الذي تم تعريفها فيه.

إذا قلنا إن الدالة العادية تشبه «وجبة جاهزة في قائمة الطعام الجاهز» (حيث تأتي المكونات من المعلمات)، فإن الإغلاق يشبه «المكونات الموجودة في ثلاجتك الخاصة» (حيث تأتي المكونات من البيئة المحيطة). يمكن للإغلاق الوصول إلى المتغيرات التي كانت موجودة وقت تعريفه — تمامًا مثل أخذ المكونات من ثلاجتك والطهي بها على الفور.


1. ما ستتعلمه



2. المخططات المفاهيمية

يوضح الرسم البياني التالي لـ «Mermaid» العلاقات والاختلافات بين الأنواع الثلاثة لالتقاط الإغلاق (Fn وFnMut وFnOnce):

100%
graph TB
    A["Closure Closure"] --> B["Fn<br/>Immutable Borrowing &T"]
    A --> C["FnMut<br/>Variable Borrowing &mut T"]
    A --> D["FnOnce<br/>Consumer Ownership T"]

    B --> E["let c = || println!(&#34;{ }&#34;, x);"]
    B --> F["Can be called multiple times"]
    B --> G["Environment variables are read-only and immutable"]

    C --> H["let mut c = || x += 1;"]
    C --> I["Can be called multiple times"]
    C --> J["Environment variables can be modified"]

    D --> K["let c = || drop(x);"]
    D --> L["Can only be called once"]
    D --> M["Ownership has been transferred to the closure"]

    B -.-> |Automatic Inference| N["The compiler, based on the closure body<br/>Select the minimum requirement trait"]
    C -.-> N
    D -.-> N
    N --> O["move Mandatory Keywords FnOnce"]


3. القصة وراء نظام تقييم خدمات توصيل الطعام

(1) الصعوبة: كتابة دالة منفصلة لكل قاعدة من قواعد التقييم

تقوم شركة «ميا» (Mia) بتطوير نظام لتقييم خدمات توصيل الطعام. يمكن للمستخدمين تقييم المطاعم، لكن قواعد التقييم تختلف:

في البداية، كتبت دالة لكل قاعدة:

RUST
struct Review {
    store: String,
    rating: u32,
}

fn normal_score(r: &Review) -> u32 {
    r.rating
}

fn blogger_score(r: &Review) -> u32 {
    r.rating * 2
}

fn inspector_score(r: &Review) -> u32 {
    if r.rating < 3 { 0 } else { r.rating }
}

fn main() {
    let reviews = vec![
        Review { store: "Pizzaplace".into(), rating: 4 },
        Review { store: "BurgerKing".into(), rating: 2 },
        Review { store: "SushiBar".into(), rating: 5 },
    ];

    for r in &reviews {
        println!("{}: normal={}, blogger={}, inspector={}",
            r.store, normal_score(r), blogger_score(r), inspector_score(r));
    }
}

وبصرف النظر عن الاختلافات في المنطق الأساسي لكل منها، فإن الدوال الثلاث لها نفس توقيعات الدوال تمامًا. فإذا قال مدير المنتج: «أضف قاعدة تقييم أخرى لـ"الأعضاء المميزين"»، فسيتعين كتابة دالة أخرى. علاوة على ذلك، لا يمكن «إنشاء» قواعد التقييم هذه «ديناميكيًا» — فعلى سبيل المثال، يجب ترميز المعامل في صيغة «النتيجة × المعامل» بشكل ثابت داخل الدالة.

(2) متطلبات أكثر تعقيدًا: عوامل ترجيح تتغير ديناميكيًا

قال مدير المنتج: «تتغير معاملات التقييم يوميًا (حسب الأنشطة الترويجية)، ويمكن للمستخدمين اختيار استراتيجيات تقييم مختلفة (التسجيل، والترجيح، وفرض عقوبات على الدرجات المنخفضة). ولا يمكن تحقيق ذلك باستخدام الدوال القياسية — حيث إن توقيعات الدوال تكون ثابتة في وقت التحويل البرمجي ولا يمكنها مراعاة المعاملات الديناميكية الخارجية».

RUST
// I want to use a function,However, the coefficient is dynamic.
let weight = 1.5;  // Today's Weighting Coefficient
// fn weighted_score(r: &Review) -> u32 {
//     (r.rating as f64 * weight) as u32  // ❌ Compilation Error:weight Outside the function's scope
// }

(3) حلول لإغلاق الصدأ

RUST
struct Review {
    store: String,
    rating: u32,
}

fn main() {
    let reviews = vec![
        Review { store: "Pizzaplace".into(), rating: 4 },
        Review { store: "BurgerKing".into(), rating: 2 },
        Review { store: "SushiBar".into(), rating: 5 },
    ];

    // Closure 1: normal score (no capture)
    let normal = |r: &Review| r.rating;

    // Closure 2: weighted score (captures `weight` from environment)
    let weight: f64 = 1.5;
    let weighted = |r: &Review| (r.rating as f64 * weight) as u32;

    // Closure 3: inspector (captures `threshold` from environment)
    let threshold = 3;
    let inspector = |r: &Review| if r.rating < threshold { 0 } else { r.rating };

    for r in &reviews {
        println!("{}: normal={}, weighted={}, inspector={}",
            r.store, normal(r), weighted(r), inspector(r));
    }
}

الناتج:

TEXT 📖 للعرض فقط
Pizzaplace: normal=4, weighted=6, inspector=4
BurgerKing: normal=2, weighted=3, inspector=0
SushiBar: normal=5, weighted=7, inspector=5

يقوم الإغلاق |r| r.rating * weight بالتقاط المتغيرات الخارجية weight — وهو أمر لا تستطيع الدوال العادية القيام به. يمكن أن تتغير weight وthreshold ديناميكيًا (على سبيل المثال، بناءً على مدخلات المستخدم أو ملفات التكوين)، ويقوم الإغلاق تلقائيًا بالتقاط قيمهما الحالية. هذه هي القيمة الأساسية للإغلاقات: دالة يمكنها «سرقة» المتغيرات من محيطها واستخدامها.



4. المفاهيم الأساسية

(1) نظرة عامة على أنظمة الإغلاق

100%
graph TB
    A[Rust Closures] --> B[Syntax]
    A --> C[Capture Modes]
    A --> D[move Keyword]
    A --> E[As Parameters]
    A --> F[With Iterators]

    B --> B1["|param1, param2| expr"]
    B --> B2["|param| { multiple; statements; }"]

    C --> C1["Fn: immutable borrow (&T)"]
    C --> C2["FnMut: mutable borrow (&mut T)"]
    C --> C3["FnOnce: ownership (T)"]

    D --> D1["let c = move || x;"]
    D --> D2["Forces ownership transfer"]

    E --> E1["fn pointer: fn(T) -> U"]
    E --> E2["Fn trait: impl Fn(T) -> U"]
    E --> E3["FnMut / FnOnce trait bounds"]

    F --> F1["iter().map(|x| x + 1)"]
    F --> F2["iter().filter(|x| x > 0)"]
    F --> F3["Chained: map().filter().collect()"]

(2) مقارنة بين أوضاع الالتقاط الثلاثة

السمة طريقة التسجيل عدد المرات التي تم استدعاؤها هل يمكن تعديل البيئة؟ هل يمكن استدعاؤها عدة مرات؟
FnOnce تستهلك الملكية (نقل) مرة واحدة يمكنها استهلاك متغيرات البيئة لا (تم نقل الملكية)
FnMut استعارة متغيرة عدة مرات يمكن تعديل متغيرات البيئة نعم
Fn استعارة غير قابلة للتغيير عدة مرات لا يمكن تعديل متغيرات البيئة نعم

(3) مؤشرات الدوال fn مقابل الإغلاقات Fn

ميزة مؤشرات الدوال fn سمة الإغلاق Fn
هل يتم التقاط البيئة؟ لا — يمكن استخدام المعلمات فقط نعم — يمكن التقاط متغيرات البيئة
هل يمكن استخدامها كمعلمة؟ نعم نعم (بشكل أعم)
هل يدعم الإغلاق؟ لا نعم
حجم النوع fn(T) -> U (حجم المؤشر) تختلف أحجام الإغلاقات باختلافها
الأداء محدد قابل للتضمين بواسطة المُجمِّع
الصيغة fn foo(x: i32) -> i32 |x| x + 1

(4) مقارنة بين طرق التقاط الإغلاق

طريقة التسجيل مثال على الصيغة الآثار المترتبة على الملكية عدد المرات التي يمكن فيها استدعاء الإغلاق السيناريوهات القابلة للتطبيق
استعارة ثابتة |x| x + var استعارة &T متعددة وصول للقراءة فقط إلى متغيرات البيئة
الاقتراض المتغير |x| { var += 1; ... } الاقتراض &mut T متعدد (حصري) يجب تعديل متغيرات البيئة
نقل الملكية move |x| x + var الحصول على T مرة واحدة فقط (في حالة الاستهلاك) يجب أن يستمر وجود الإغلاق بعد انتهاء صلاحية المرجع
لا يوجد التقاط |x| x + 1 لا شيء متعدد يعادل مؤشر الدالة


5. أمثلة على عمليات الإغلاق

(1) ▶ المثال:الصيغة الأساسية للـ«إغلاق» — تقييمات خدمات توصيل الطعام (مستوى الصعوبة ⭐)

RUST
// ============================================
// Closure basics: syntax, type inference, calling
// ============================================

struct Review {
    store: String,
    rating: u32,
}

fn main() {
    let reviews = vec![
        Review { store: String::from("Pizzaplace"), rating: 4 },
        Review { store: String::from("BurgerKing"), rating: 2 },
        Review { store: String::from("SushiBar"), rating: 5 },
        Review { store: String::from("NoodleHouse"), rating: 3 },
    ];

    // --- Syntax variation 1: single expression ---
    let double = |r: &Review| r.rating * 2;
    // --- Syntax variation 2: block body ---
    let triple = |r: &Review| {
        let result = r.rating * 3;
        println!("    (triple: {} -> {})", r.rating, result);
        result
    };
    // --- Syntax variation 3: type inference ---
    // Rust infers parameter and return types from usage
    let half = |r| r.rating / 2;  // type inferred as &Review -> u32

    println!("--- Rating Calculations ---");
    for r in &reviews {
        println!("{} (rating: {}):", r.store, r.rating);
        println!("  double={}, half={}", double(r), half(r));
        let _ = triple(r);
    }

    // --- Closures that capture variables ---
    let min_rating = 3;
    let min_rating2 = 4;

    // Capture `min_rating` from the surrounding scope
    let is_pass = |r: &Review| r.rating >= min_rating;
    let is_good = |r: &Review| r.rating >= min_rating2;

    println!("\n--- Pass Check (min: {}) ---", min_rating);
    for r in &reviews {
        println!("{}: {}", r.store, if is_pass(r) { "PASS" } else { "FAIL" });
    }

    println!("\n--- Good Check (min: {}) ---", min_rating2);
    for r in &reviews {
        println!("{}: {}", r.store, if is_good(r) { "GOOD" } else { "OK" });
    }

    // --- Using closures as function arguments ---
    fn check_reviews(reviews: &[Review], label: &str, scorer: impl Fn(&Review) -> u32) {
        print!("{}: ", label);
        for r in reviews {
            print!("{}->{} ", r.store, scorer(r));
        }
        println!();
    }

    let weight = 2;
    let weighted_scorer = |r: &Review| r.rating * weight;

    check_reviews(&reviews, "Weighted(×2)", weighted_scorer);
    check_reviews(&reviews, "Normal", |r| r.rating);
    check_reviews(&reviews, "Bonus", |r| if r.rating >= 4 { r.rating + 1 } else { r.rating });
}

الناتج:

TEXT 📖 للعرض فقط
--- Rating Calculations ---
Pizzaplace (rating: 4):
  double=8, half=2
    (triple: 4 -> 12)
BurgerKing (rating: 2):
  double=4, half=1
    (triple: 2 -> 6)
SushiBar (rating: 5):
  double=10, half=2
    (triple: 5 -> 15)
NoodleHouse (rating: 3):
  double=6, half=1
    (triple: 3 -> 9)

--- Pass Check (min: 3) ---
Pizzaplace: PASS
BurgerKing: FAIL
SushiBar: PASS
NoodleHouse: PASS

--- Good Check (min: 4) ---
Pizzaplace: GOOD
BurgerKing: OK
SushiBar: GOOD
NoodleHouse: OK

--- Weighted(×2) ---
Pizzaplace->8 BurgerKing->4 SushiBar->10 NoodleHouse->6
Normal: Pizzaplace->4 BurgerKing->2 SushiBar->5 NoodleHouse->3
Bonus: Pizzaplace->5 BurgerKing->2 SushiBar->6 NoodleHouse->3

هناك ثلاثة أنماط لصيغة الإغلاق: التعبير الفردي |x| expr، والكتلة |x| { stmt; expr }، واستدلال النوع |x| x+1 (حيث يُستدل على النوع من السياق). التقاط متغيرات البيئة هو إحدى القدرات الفريدة للإغلاقات: is_pass تلتقط min_rating، وweighted_scorer تلتقط weight. تقبل الدالة check_reviews impl Fn(&Review) -> u32 — أي إغلاق ينفذ السمة Fn يمكن تمريره إليها.


(2) ▶ المثال:وضع الالتقاط — Fn / FnMut / FnOnce (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Three capture modes: Fn, FnMut, FnOnce
// ============================================

fn main() {
    println!("=== Fn: Immutable Borrow ===");
    let scores = vec![10, 20, 30];
    // Fn closure: only reads captured variables (immutable borrow)
    let print_scores = || {
        println!("Scores: {:?}", scores);  // &Vec<i32>
    };
    print_scores();  // Can be called multiple times
    print_scores();
    println!("Still accessible: {:?}", scores);  // scores not moved

    println!("\n=== FnMut: Mutable Borrow ===");
    let mut counters = vec![0, 0, 0];
    // FnMut closure: can mutate captured variables
    let mut increment = || {
        for c in &mut counters {
            *c += 1;
        }
    };
    increment();  // Can be called multiple times
    increment();
    increment();
    println!("Counters: {:?}", counters);  // [3, 3, 3]

    println!("\n=== FnOnce: Ownership Consumed ===");
    let name = String::from("Pizzaplace");
    // FnOnce closure: consumes the captured variable
    let consume = || {
        println!("Consuming: {}", name);
        drop(name);  // Explicitly drop (consumes ownership)
    };
    consume();
    // consume();  // ❌ Compile error: closure can only be called once
    // println!("{}", name);  // ❌ Compile error: name was moved

    println!("\n=== FnOnce with move ===");
    let data = vec![1, 2, 3, 4, 5];
    // `move` forces the closure to take ownership of `data`
    let compute = move || {
        let sum: i32 = data.iter().sum();
        println!("Sum of data: {}", sum);
        // data is dropped here at end of closure
    };
    compute();
    // println!("{:?}", data);  // ❌ Compile error: data was moved into the closure
    // compute();  // ❌ Could also fail if the closure consumed data

    println!("\n=== Practical: Closure Type Selection ===");
    let items = vec!["apple", "banana", "cherry"];

    // Fn: read-only
    let count = || items.len();
    println!("Count (Fn): {}", count());

    // FnMut: modify captured variable
    let mut store = String::new();
    let mut append = |item: &str| {
        if !store.is_empty() { store.push_str(", "); }
        store.push_str(item);
    };
    for item in &items {
        append(item);
    }
    println!("Store (FnMut modified): {}", store);
    let store_len = store.len();
    println!("Store length: {}", store_len);
}

الناتج:

TEXT 📖 للعرض فقط
=== Fn: Immutable Borrow ===
Scores: [10, 20, 30]
Scores: [10, 20, 30]
Still accessible: [10, 20, 30]

=== FnMut: Mutable Borrow ===
Counters: [3, 3, 3]

=== FnOnce: Ownership Consumed ===
Consuming: Pizzaplace

=== FnOnce with move ===
Sum of data: 15

=== Practical: Closure Type Selection ===
Count (Fn): 3
Store (FnMut modified): apple, banana, cherry
Store length: 20

يستنتج المُجمِّع تلقائيًا السمة التي يجب استخدامها بناءً على الطريقة التي يصل بها نص الإغلاق إلى المتغيرات الملتقطة: الوصول للقراءة فقطFn، الوصول القابل للتعديلFnMut (يجب إعلان الإغلاق باستخدام mutاستهلاك الملكيةFnOnce (لا يمكن استدعاؤها إلا مرة واحدة). الكلمة الرئيسية move تفرض نقل الملكية إلى الإغلاق — وتُستخدم عادةً في سيناريوهات تعدد الخيوط (thread::spawn تتطلب مدة حياة 'static).


(3) ▶ المثال:الإغلاقات كمعاملات — مؤشرات الدوال مقابل السمة Fn (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Closures as parameters: fn pointer vs Fn trait
// ============================================

// --- Version 1: Accepts ONLY function pointers ---
// fn(T) -> U is a function pointer type (no environment capture)
fn apply_fn_pointer(f: fn(i32) -> i32, x: i32) -> i32 {
    f(x)
}

// --- Version 2: Accepts ANY Fn trait (including closures) ---
// impl Fn(T) -> U accepts both fn pointers and closures
fn apply_fn<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(x)
}

// --- Version 3: FnMut parameter ---
fn apply_twice<F>(mut f: F, x: i32) -> i32
where
    F: FnMut(i32) -> i32,
{
    f(f(x))
}

// --- Version 4: FnOnce parameter ---
fn apply_once<F>(f: F, x: i32) -> i32
where
    F: FnOnce(i32) -> i32,
{
    f(x)
}

// A regular function (can be used as fn pointer)
fn square(x: i32) -> i32 {
    x * x
}

fn main() {
    // --- Regular functions as fn pointers ---
    println!("--- Function Pointers ---");
    // `square` is a function, can be passed as fn(i32) -> i32
    println!("square(5) via fn ptr: {}", apply_fn_pointer(square, 5));

    // Annotated closure (no capture) can be coerced to fn pointer
    let triple = |x: i32| x * 3;
    println!("triple(5) via fn ptr: {}", apply_fn_pointer(triple, 5));

    // --- Closures with Fn trait ---
    println!("\n--- Fn Trait (generic) ---");
    let base = 10;
    // This closure captures `base`, so it CANNOT be a fn pointer
    let add_base = |x: i32| x + base;
    println!("add_base(5): {}", apply_fn(add_base, 5));

    // Both fn pointers and closures work with Fn trait
    println!("square(6) via Fn: {}", apply_fn(square, 6));

    // --- FnMut in action ---
    println!("\n--- FnMut ---");
    let mut accum = 0;
    let mut accumulate = |x: i32| {
        accum += x;
        accum
    };
    println!("accumulate(5): {}", apply_twice(&mut accumulate, 5));
    // After apply_twice: accum = 5 + 5 = 10, then 10 + 5 = 15
    println!("Final accum: {}", accum);

    // --- FnOnce in action ---
    println!("\n--- FnOnce ---");
    let owned = String::from("value: ");
    let describe = |x: i32| {
        println!("{} {}", owned, x);
        x
    };
    println!("describe(42): {}", apply_once(describe, 42));
    // describe was consumed (FnOnce), cannot call again

    // --- Practical: scoring system with different strategies ---
    println!("\n--- Practical: Scoring Strategies ---");

    fn run_strategy<F>(reviews: &[u32], label: &str, strategy: F)
    where
        F: Fn(u32) -> u32,
    {
        let scores: Vec<u32> = reviews.iter().map(|&r| strategy(r)).collect();
        println!("{}: {:?}", label, scores);
    }

    let ratings = [4, 2, 5, 3, 1];
    let bonus_threshold = 4;
    let bonus_points = 1;

    // Different scoring strategies, all as closures
    run_strategy(&ratings, "Normal", |r| r);
    run_strategy(&ratings, "Double", |r| r * 2);
    run_strategy(&ratings, "Bonus", |r| if r >= bonus_threshold { r + bonus_points } else { r });
    run_strategy(&ratings, "Penalty", |r| if r < 3 { 0 } else { r });
}

الناتج:

TEXT 📖 للعرض فقط
--- Function Pointers ---
square(5) via fn ptr: 25
triple(5) via fn ptr: 15

--- Fn Trait (generic) ---
add_base(5): 15
square(6) via Fn: 36

--- FnMut ---
accumulate(5): 15
Final accum: 15

--- FnOnce ---
value:  42
describe(42): 42

--- Practical: Scoring Strategies ---
Normal: [4, 2, 5, 3, 1]
Double: [8, 4, 10, 6, 2]
Bonus: [5, 2, 6, 3, 1]
Penalty: [4, 0, 5, 3, 0]

لا يمكن لمؤشرات الدوال fn(i32) -> i32 أن تقبل سوى الدوال العادية والإغلاقات التي لا تلتقط البيئة. أما السمة Fn (العامة) فيمكنها قبول أي إغلاق (بما في ذلك تلك التي تلتقط البيئة). قواعد الاختيار: استخدم مؤشرات fn لـ FFI أو عندما تحتاج إلى تخزين نوع دالة ثابت؛ وقبل السمات Fn/FnMut/FnOnce في السيناريوهات التي تتطلب إغلاقات تلتقط البيئة. تقبل الدوال run_strategy impl Fn(u32) -> u32 — أربع استراتيجيات تسجيل نقاط مختلفة — كإغلاقات، بما في ذلك استراتيجيات المكافأة التي تلتقط bonus_threshold وbonus_points.


(4) ▶ المثال:الجمع بين الإغلاقات والمكررات — map / filter / collect (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Closures + Iterator combinators: map, filter, collect
// ============================================

#[derive(Debug)]
struct Order {
    store: String,
    items: Vec<String>,
    rating: u32,
}

fn main() {
    let orders = vec![
        Order {
            store: String::from("Pizzaplace"),
            items: vec!["Margherita".into(), "Cola".into()],
            rating: 4,
        },
        Order {
            store: String::from("BurgerKing"),
            items: vec!["Whopper".into(), "Fries".into(), "Shake".into()],
            rating: 2,
        },
        Order {
            store: String::from("SushiBar"),
            items: vec!["Salmon".into(), "Tuna".into()],
            rating: 5,
        },
        Order {
            store: String::from("NoodleHouse"),
            items: vec!["Ramen".into(), "Gyoza".into(), "Tea".into()],
            rating: 3,
        },
    ];

    // --- map: transform each element ---
    println!("--- map: store names uppercase ---");
    let store_names: Vec<String> = orders
        .iter()
        .map(|o| o.store.to_uppercase())
        .collect();
    println!("{:?}", store_names);

    // --- filter: keep elements matching a condition ---
    println!("\n--- filter: orders with rating >= 4 ---");
    let good_orders: Vec<&Order> = orders
        .iter()
        .filter(|o| o.rating >= 4)
        .collect();
    for o in &good_orders {
        println!("{:?}", o);
    }

    // --- filter + map: chained ---
    println!("\n--- filter + map: good store names ---");
    let good_stores: Vec<String> = orders
        .iter()
        .filter(|o| o.rating >= 4)
        .map(|o| format!("★ {} (rating: {})", o.store, o.rating))
        .collect();
    for s in &good_stores {
        println!("{}", s);
    }

    // --- map + filter: compute and refine ---
    println!("\n--- map + filter: average rating per item ---");
    let avg_ratings: Vec<String> = orders
        .iter()
        .map(|o| {
            // Compute average rating per item
            let item_count = o.items.len() as f64;
            let avg = o.rating as f64 / item_count;
            (o.store.clone(), avg, o.items.len())
        })
        .filter(|(_, avg, _)| *avg > 1.5)  // Only stores with high per-item rating
        .map(|(store, avg, count)| format!("{}: {:.2}/item ({} items)", store, avg, count))
        .collect();
    for s in &avg_ratings {
        println!("{}", s);
    }

    // --- Advanced: closures capturing external state ---
    println!("\n--- Advanced: dynamic threshold filtering ---");
    let min_rating = 3;
    let category = "Premium";

    let premium_stores: Vec<String> = orders
        .iter()
        .filter(|o| o.rating >= min_rating)
        .map(|o| format!("[{}] {} (rating: {})", category, o.store, o.rating))
        .collect();

    for s in &premium_stores {
        println!("{}", s);
    }

    // --- All items from high-rated stores ---
    println!("\n--- Flat items from high-rated stores ---");
    let threshold = 3;
    let recommended_items: Vec<&String> = orders
        .iter()
        .filter(|o| o.rating > threshold)
        .flat_map(|o| o.items.iter())
        .collect();
    println!("Recommended: {:?}", recommended_items);

    // --- Custom scoring with filter ---
    println!("\n--- Custom filter: weighted score ---");
    let weight = 1.2;
    let min_score = 4.0;

    let top_orders: Vec<&Order> = orders
        .iter()
        .filter(|o| (o.rating as f64 * weight) >= min_score)
        .collect();
    for o in &top_orders {
        let weighted = o.rating as f64 * weight;
        println!("{}: raw={}, weighted={:.1}, pass={}", o.store, o.rating, weighted, weighted >= min_score);
    }

    // --- count, any, all ---
    println!("\n--- Aggregate checks ---");
    let high_rated_count = orders.iter().filter(|o| o.rating >= 4).count();
    println!("Orders with rating >= 4: {}", high_rated_count);

    let has_bad_order = orders.iter().any(|o| o.rating <= 1);
    println!("Has any bad order (rating <= 1): {}", has_bad_order);

    let all_rated = orders.iter().all(|o| o.rating >= 1);
    println!("All orders rated at least 1: {}", all_rated);
}

الناتج:

TEXT 📖 للعرض فقط
--- map: store names uppercase ---
["PIZZAPLACE", "BURGERKING", "SUSHIBAR", "NOODLEHOUSE"]

--- filter: orders with rating >= 4 ---
Order { store: "Pizzaplace", items: ["Margherita", "Cola"], rating: 4 }
Order { store: "SushiBar", items: ["Salmon", "Tuna"], rating: 5 }

--- filter + map: good store names ---
★ Pizzaplace (rating: 4)
★ SushiBar (rating: 5)

--- map + filter: average rating per item ---
Pizzaplace: 2.00/item (2 items)
SushiBar: 2.50/item (2 items)

--- Advanced: dynamic threshold filtering ---
[Premium] Pizzaplace (rating: 4)
[Premium] SushiBar (rating: 5)
[Premium] NoodleHouse (rating: 3)

--- Flat items from high-rated stores ---
Recommended: ["Margherita", "Cola", "Salmon", "Tuna"]

--- Custom filter: weighted score ---
Pizzaplace: raw=4, weighted=4.8, pass=true
SushiBar: raw=5, weighted=6.0, pass=true

--- Aggregate checks ---
Orders with rating >= 4: 2
Has any bad order (rating <= 1): false
All orders rated at least 1: true

مُركِّبات المُكرِّر هي أحد أقوى تطبيقات الإغلاقات: map (تحويل كل عنصر)، filter (الاحتفاظ بالعناصر بناءً على شروط)، flat_map (تسوية المكررات المتداخلة)، any/all (فحوصات التجميع)، count (العد). تكمن قيمة الإغلاقات هنا في قدرتها على التقاط القيم الديناميكية الخارجية (مثل min_rating وweight وcategory)، مما يؤدي إلى فصل منطق معالجة البيانات عن التكوين. وتعد الاستدعاءات المتسلسلة iter().filter().map().collect() من بين العبارات الأكثر تعبيرًا في لغة Rust.


(5) ▶ المثال:تمرين شامل — تنفيذ مسار بيانات يعتمد على التكوين باستخدام الإغلاقات (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example: Closure Capture + Higher-Order Functions + Iterator Chain
// ============================================

struct DataPipeline<'a, T> {
    data: Vec<T>,
    filters: Vec<Box<dyn Fn(&T) -> bool + 'a>>,
    transforms: Vec<Box<dyn Fn(T) -> T + 'a>>,
}

impl<'a, T: Clone + 'a> DataPipeline<'a, T> {
    fn new(data: Vec<T>) -> Self {
        DataPipeline { data, filters: Vec::new(), transforms: Vec::new() }
    }

    fn filter<F: Fn(&T) -> bool + 'a>(mut self, f: F) -> Self {
        self.filters.push(Box::new(f));
        self
    }

    fn map<F: Fn(T) -> T + 'a>(mut self, f: F) -> Self {
        self.transforms.push(Box::new(f));
        self
    }

    fn execute(self) -> Vec<T> {
        let mut result = self.data;
        for f in &self.filters {
            result.retain(f);
        }
        for t in &self.transforms {
            result = result.into_iter().map(t).collect();
        }
        result
    }
}

fn main() {
    let min_score = 60;
    let bonus = 10;
    let max_score = 100;

    let pipeline = DataPipeline::new(vec![85, 42, 95, 58, 73, 30, 88])
        .filter(move |&&x| x >= min_score)
        .filter(|&&x| x < max_score)
        .map(move |x| x + bonus);

    let result = pipeline.execute();
    println!("Filter+ After the bonus points are added: {:?}", result);

    let multiplier = 2;
    let adjusted: Vec<i32> = result.into_iter()
        .map(|x| x * multiplier)
        .filter(|x| *x < 200)
        .collect();
    println!("Double+ Upper Limit Filtering: {:?}", adjusted);

    let threshold = 150;
    let has_high = adjusted.iter().any(|&x| x > threshold);
    let all_above_100 = adjusted.iter().all(|&x| x > 100);
    println!("Has values greater than {}: {}, All > 100: {}", threshold, has_high, all_above_100);
}

الناتج:

TEXT 📖 للعرض فقط
Filter+ After the bonus points are added: [95, 83, 98]
Double+ Upper Limit Filtering: [190, 166, 196]
Has values greater than 150: true, All > 100: true

يستخدم DataPipeline Box<dyn Fn> لتخزين الإغلاقات، ويقوم move بالتقاط قيم التكوين الخارجية (min_score، bonus)، بينما تُشكّل المكالمات المتسلسلة إلى filter().map() مسار المعالجة. وتتيح الإغلاقات «فصل التكوين عن المنطق».



❓ أسئلة شائعة

س ما الفرق الأساسي بين الإغلاقات والدوال العادية؟
ج يمكن للإغلاقات التقاط المتغيرات من النطاق المحيط بها؛ بينما لا تستطيع الدوال العادية ذلك.
س كيف يختار المُجمِّع بين Fn و FnMut و FnOnce؟
ج يستنتج المُجمِّع ذلك تلقائيًا بناءً على الطريقة التي يصل بها جسم الإغلاق إلى المتغيرات الملتقطة.
س متى يجب استخدام الكلمة الرئيسية move؟
ج عندما تحتاج إلى أن يمتلك الإغلاق المتغيرات الملتقطة، خاصة في سيناريوهات تعدد الخيوط.
س هل يمكن استخدام مؤشرات الدوال fn(T) -> U وسمات الإغلاق Fn(T) -> U بشكل متبادل؟
ج لا يمكن استبدالهما بشكل كامل. لا يمكن لمؤشرات الدوال التقاط البيئة؛ بينما يمكن للإغلاقات ذلك.
س عند استخدام الإغلاقات كمعلمات، هل يجب أن أختار impl Fn أم Box<dyn Fn> أم F: Fn العام؟
ج القيد العام F: Fn هو الأفضل من حيث الأداء (التوزيع الثابت)، وBox<dyn Fn> هو الأكثر مرونة (التوزيع الديناميكي)، وimpl Fn هو عبارة عن "سكر نحوي" للأنواع العامة.
س ما هو أداء الإغلاق؟ هل هو أبطأ من الدالة العادية؟
ج تعمل الإغلاقات التي لا تلتقط البيئة بنفس أداء الدوال العادية تمامًا. أما الإغلاقات التي تلتقط البيئة، فإن عبءها الإضافي يساوي صفرًا أو يقارب الصفر.

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: يوجد متغير Vec<i32> يحتوي على [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. اكتب إغلاقًا يلتقط المتغير threshold (المُعيَّن بقيمة 5)، واستخدم filter لتصفية الأعداد الأكبر من threshold، واستخدم map لمضاعفتها، وأخيرًا استخدم collect لإضافتها إلى متجه جديد (Vec) وطباعة النتيجة.

  2. الصعوبة ⭐⭐: اكتب دالة fn transform<F>(data: &[i32], f: F) -> Vec<i32> where F: Fn(i32) -> i32 تُطبق دالة تحويل على كل عنصر. في الدالة main، أنشئ ثلاثة إغلاقات: |x| x * 2 (double)، وإغلاق جمع يلتقط المتغير add، وإغلاق اقتطاع يلتقط المتغير max_val (يعيد max_val إذا تجاوزت القيمة max_val). استدعِ transform لكل منها واطبع النتائج.

  3. الصعوبة ⭐⭐⭐: توجد بنية تُسمى struct Product { name: String, price: f64, category: String }. قم بإنشاء 6 مثيلات للمنتجات وتخزينها في متجه. اكتب دالة fn analyze_products<F1, F2>(products: &[Product], category_filter: F1, price_adjuster: F2) where F1: Fn(&&Product) -> bool, F2: Fn(f64) -> f64 تقوم أولاً بالتصفية حسب الفئة باستخدام filter، ثم تعديل السعر باستخدام map، وأخيراً فرز المنتجات حسب أسعارها المعدلة وطباعة أغلى 3 منتجات. في الدالة main، اختبر ذلك مرتين على الأقل باستخدام استراتيجيات مختلفة للتصفية وتعديل الأسعار.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%