Rust: الإغلاقات في لغة Rust
آخر تحديث: 2026-08-26
الإغلاق هو دالة مجهولة يمكنها التقاط متغيرات من البيئة المحيطة بها. يتم استدعاؤها تمامًا مثل أي دالة، لكنها قادرة على «تذكر» المتغيرات الموجودة في النطاق الذي تم تعريفها فيه.
إذا قلنا إن الدالة العادية تشبه «وجبة جاهزة في قائمة الطعام الجاهز» (حيث تأتي المكونات من المعلمات)، فإن الإغلاق يشبه «المكونات الموجودة في ثلاجتك الخاصة» (حيث تأتي المكونات من البيئة المحيطة). يمكن للإغلاق الوصول إلى المتغيرات التي كانت موجودة وقت تعريفه — تمامًا مثل أخذ المكونات من ثلاجتك والطهي بها على الفور.
1. ما ستتعلمه
- كيفية كتابة صيغة الإغلاق
|params| exprو|params| { block } - ثلاثة أوضاع للاحتجاز:
Fn(الاقتراض غير القابل للتغيير)،FnMut(الاقتراض القابل للتغيير)،FnOnce(استهلاك الملكية) moveالكلمة الرئيسية — تُفرض بموجبها نقل ملكية المتغيرات الملتقطة إلى الإغلاق- الإغلاقات كمعلمات للدوال: الفرق بين مؤشرات الدوال
fnوالسمةFn - الجمع بين الإغلاقات والمكررات:
map،filter،collect— استدعاءات متسلسلة - أنماط استخدام الإغلاقات في سيناريوهات واقعية
2. المخططات المفاهيمية
يوضح الرسم البياني التالي لـ «Mermaid» العلاقات والاختلافات بين الأنواع الثلاثة لالتقاط الإغلاق (Fn وFnMut وFnOnce):
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!("{ }", 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) بتطوير نظام لتقييم خدمات توصيل الطعام. يمكن للمستخدمين تقييم المطاعم، لكن قواعد التقييم تختلف:
- المستخدمون المنتظمون: قوموا بالتقييم مباشرةً باستخدام 1–5 نجوم
- مدون طعام: التقييم × 2 (وزن أكبر)
- مفتش الصحة: تُصنف الدرجات التي تقل عن 3 على أنها «تحتاج إلى تصحيح»
في البداية، كتبت دالة لكل قاعدة:
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) متطلبات أكثر تعقيدًا: عوامل ترجيح تتغير ديناميكيًا
قال مدير المنتج: «تتغير معاملات التقييم يوميًا (حسب الأنشطة الترويجية)، ويمكن للمستخدمين اختيار استراتيجيات تقييم مختلفة (التسجيل، والترجيح، وفرض عقوبات على الدرجات المنخفضة). ولا يمكن تحقيق ذلك باستخدام الدوال القياسية — حيث إن توقيعات الدوال تكون ثابتة في وقت التحويل البرمجي ولا يمكنها مراعاة المعاملات الديناميكية الخارجية».
// 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) حلول لإغلاق الصدأ
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));
}
}
الناتج:
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) نظرة عامة على أنظمة الإغلاق
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) ▶ المثال:الصيغة الأساسية للـ«إغلاق» — تقييمات خدمات توصيل الطعام (مستوى الصعوبة ⭐)
// ============================================
// 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 });
}
الناتج:
--- 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_reviewsimpl Fn(&Review) -> u32— أي إغلاق ينفذ السمةFnيمكن تمريره إليها.
(2) ▶ المثال:وضع الالتقاط — Fn / FnMut / FnOnce (مستوى الصعوبة ⭐⭐)
// ============================================
// 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);
}
الناتج:
=== 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 (مستوى الصعوبة ⭐⭐)
// ============================================
// 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 });
}
الناتج:
--- 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_strategyimpl Fn(u32) -> u32— أربع استراتيجيات تسجيل نقاط مختلفة — كإغلاقات، بما في ذلك استراتيجيات المكافأة التي تلتقطbonus_thresholdوbonus_points.
(4) ▶ المثال:الجمع بين الإغلاقات والمكررات — map / filter / collect (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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);
}
الناتج:
--- 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) ▶ المثال:تمرين شامل — تنفيذ مسار بيانات يعتمد على التكوين باستخدام الإغلاقات (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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);
}
الناتج:
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
يستخدم
DataPipelineBox<dyn Fn>لتخزين الإغلاقات، ويقومmoveبالتقاط قيم التكوين الخارجية (min_score،bonus)، بينما تُشكّل المكالمات المتسلسلة إلىfilter().map()مسار المعالجة. وتتيح الإغلاقات «فصل التكوين عن المنطق».
❓ أسئلة شائعة
move؟fn(T) -> U وسمات الإغلاق Fn(T) -> U بشكل متبادل؟impl Fn أم Box<dyn Fn> أم F: Fn العام؟F: Fn هو الأفضل من حيث الأداء (التوزيع الثابت)، وBox<dyn Fn> هو الأكثر مرونة (التوزيع الديناميكي)، وimpl Fn هو عبارة عن "سكر نحوي" للأنواع العامة.📖 ملخص
- صيغة الإغلاق
|params| exprأو|params| { block }؛ وعادةً ما يتم استنتاج أنواع المعلمات والقيم المرجعة - ثلاثة أوضاع للاستحواذ:
Fn(استعارة غير قابلة للتغيير، يمكن استدعاؤها عدة مرات)،FnMut(استعارة قابلة للتغيير، يمكن استدعاؤها عدة مرات)،FnOnce(تستهلك الملكية، لا يمكن استدعاؤها إلا مرة واحدة) moveتُفرض هذه الكلمة الرئيسية نقل ملكية المتغيرات الملتقطة إلى الإغلاق — وهي ممارسة معتادة في البرمجة متعددة الخيوط- يمكن لمعلمات الدوال قبول الإغلاقات: مؤشرات
fn(بدون التقاط) أو سماتFn/FnMut/FnOnce(عامة) - الجمع بين الإغلاق والمكرر (
map،filter،flat_map،any،all) هو أحد أقوى الأساليب البرمجية في لغة Rust - تُعد الإغلاقات في لغة Rust تجريدات بدون تكلفة — يتم تضمينها مباشرةً في وقت التحويل البرمجي، دون أي عبء إضافي في وقت التشغيل
📝 تمارين
-
الصعوبة ⭐: يوجد متغير
Vec<i32>يحتوي على[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. اكتب إغلاقًا يلتقط المتغيرthreshold(المُعيَّن بقيمة 5)، واستخدمfilterلتصفية الأعداد الأكبر منthreshold، واستخدمmapلمضاعفتها، وأخيرًا استخدمcollectلإضافتها إلى متجه جديد (Vec) وطباعة النتيجة. -
الصعوبة ⭐⭐: اكتب دالة
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لكل منها واطبع النتائج. -
الصعوبة ⭐⭐⭐: توجد بنية تُسمى
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، اختبر ذلك مرتين على الأقل باستخدام استراتيجيات مختلفة للتصفية وتعديل الأسعار.