Rust: مكررات Rust: مسارات معالجة البيانات المؤجلة

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

المُكرر هو «مسار معالجة البيانات المؤجلة» في لغة Rust — فهو لا يحسب النتائج على الفور، بل يُنتج العناصر واحدة تلو الأخرى، مما يتيح لك معالجة تسلسلات البيانات بطريقة إعلانية من خلال سلسلة من الاستدعاءات.

المكرر يشبه خط التجميع في المصنع: تدخل البيانات من أحد طرفيه، وتمر بسلسلة من العمليات (التصفية، والتحويل، والاستخراج، والتجميع)، ثم تُنتج في النهاية كمنتج نهائي من الطرف الآخر. لا تقوم كل عملية إلا بشيء واحد، ولكن عند دمجها معًا، يمكنها إنجاز مهام معالجة معقدة.


1. قصة مصنع يعمل بنظام خط التجميع

(1) الصعوبة: معالجة البيانات باستخدام الحلقات عملية مملة وتستغرق وقتًا طويلاً

شياو مينغ هو مشرف خط التجميع في مصنع «راست». وهو بحاجة إلى معالجة مجموعة من بيانات قطع الغيار:

  1. فرز جميع المنتجات المؤهلة (القطع ذات الأرقام الزوجية)
  2. ضع علامة على كل جزء مرتين
  3. اختر أول 5 فقط
  4. المجاميع الإحصائية

وقد كتبها باستخدام حلقة for التقليدية:

RUST
fn main() {
    let parts = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let mut result = vec![];
    let mut count = 0;

    for &part in &parts {
        if part % 2 == 0 {           // Steps1: Filter
            let doubled = part * 2;  // Steps2: Convert
            result.push(doubled);
            count += 1;
            if count == 5 {          // Steps3: Excerpt
                break;
            }
        }
    }

    let sum: i32 = result.iter().sum();
    println!("Result: {:?}, Sum: {}", result, sum);
}

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

(2) نهج مسار المكرر

أعد كتابة المنطق المذكور أعلاه باستخدام استدعاءات متسلسلة للمكرر:

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

    let sum: i32 = parts.iter()
        .filter(|&&n| n % 2 == 0)   // Steps1: Filter for Even Numbers
        .map(|&n| n * 2)             // Steps2: multiply by2
        .take(5)                     // Steps3: Take the first 5
        .sum();                      // Steps4: Sum

    println!("Sum: {}", sum);
}

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



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

يوضح الرسم التخطيطي التالي الذي يُعرف باسم «Mermaid» عملية التقييم المتأخر الكاملة لسلسلة أساليب سمة «Iterator» بدءًا من iter() وصولاً إلى collect():

100%
graph LR
    A["Raw Data<br/>1..=20"] --> B["iter()<br/>Create an iterator"]
    B --> C["filter(|n| n%2==0)<br/>Filter for Even Numbers"]
    C --> D["map(|n| n*3)<br/>Each element ×3"]
    D --> E["skip(2)<br/>Skip the first 2"]
    E --> F["take(5)<br/>Take the first 5"]
    F --> G["collect()<br/>Consumer: Triggered Evaluation"]

    H["Lazy Evaluation: Build the pipeline only<br/>Do not calculate immediately"] -.-> C
    H -.-> D
    H -.-> E
    H -.-> F
    I["Consumer-Driven Execution<br/>Produce the final result"] -.-> G

    G --> J["Results<br/>[18, 24, 30, 36, 42]"]


3. ما ستتعلمه



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

100%
graph TB
    A[Iterator Iterator] --> B[Iterator Adapters<br>Adapter]
    A --> C[Consumer Consumer]

    B --> D["map(|x| x+1) Convert"]
    B --> E["filter(|x| x>0) Filter"]
    B --> F["take(n) Take the first n"]
    B --> G["skip(n) Skip n"]
    B --> H["chain(other) Concat"]
    B --> I["enumerate() Add an index"]

    C --> J["collect() Collected into a set"]
    C --> K["sum() Sum"]
    C --> L["count() Count"]
    C --> M["fold(init, fn) Collapse"]
    C --> N["for x in iter Loop"]

    B -.->|"❌ Inertia<br>If it isn't called, it won't run."| C
    C --> O["Triggered Evaluation"]

(1) محولات المكرر مقابل المستهلكين

ميزة محول المكرر المستهلك
الوظيفة تحويل مُكرر (من مُكرر إلى آخر) تشغيل المُكرر وإخراج النتيجة النهائية
القيمة المرجعة النوع الجديد لـ Iterator القيم المحددة (على سبيل المثال، Vec<T>، i32، usize)
كسول كسول (لا يُنفَّذ على الفور) جشع (يُنفَّذ على الفور)
الطريقة النموذجية map، filter، take، skip، chain collect، sum، count، fold، for_each
موضع السلسلة الخطوة الوسيطة الخطوة النهائية
مثال `.map( x

(2) مرجع سريع لمحولات المكرر الشائعة

المحول الوظيفة المثال النتيجة
map(f) تطبيق التحويل f على كل عنصر [1,2,3].iter().map(|x| x*2) 2, 4, 6
filter(p) الاحتفاظ بالعناصر التي تستوفي الشرط p [1..=5].filter(|x| x%2==0) 2, 4
take(n) اختر العناصر n الأولى فقط [1..].take(3) 1, 2, 3
skip(n) تخطي العناصر n الأولى [1..=5].skip(2) 3, 4, 5
chain(it) إضافة مُكرر آخر [1,2].iter().chain([3,4].iter()) 1, 2, 3, 4
enumerate() تعيين مؤشر لكل عنصر (i, val) ['a','b'].iter().enumerate() (0,'a'), (1,'b')
zip(it) قم بإقران المكررين واحدًا تلو الآخر [1,2].iter().zip(['a','b'].iter()) (1,'a'), (2,'b')
rev() المُكرر العكسي [1..=3].rev() 3, 2, 1

(3) مرجع سريع للطرق الشائعة المستخدمة من قبل المستهلكين

المستهلك التأثير مثال نوع القيمة المرجعة هل هي طريقة جشعة
collect() جمعها كمجموعة iter.collect::<Vec<_>>() B: FromIterator نعم
sum() المجموع iter.sum::<i32>() S: Sum نعم
count() العدد iter.count() usize نعم
fold(init, f) الحساب التراكمي iter.fold(0, |acc, x| acc + x) نوع القيمة الأولية نعم
reduce(f) الانكماش بدون قيمة أولية iter.reduce(|a, b| a + b) Option<Item> نعم
for_each(f) التنفيذ واحدًا تلو الآخر (بدون قيمة إرجاع) iter.for_each(|x| println!(x)) () نعم
any(p) هل توجد مطابقة؟ iter.any(|x| x > 0) bool نعم
all(p) هل تم استيفاء جميع الشروط؟ iter.all(|x| x > 0) bool نعم
find(p) ابحث عن أول ما يستوفي المعايير iter.find(|x| *x > 3) Option<Item> نعم
max() / min() الحد الأقصى/الحد الأدنى iter.max() Option<Item> نعم


5. أمثلة

(1) ▶ المثال:سمة Iterator وطريقة next() — فهم جوهر المُكرِّرات (مستوى الصعوبة ⭐)

RUST
// ============================================
// Manual Invocation next() Understanding How Iterators Work
// ============================================

fn main() {
    let numbers = vec![10, 20, 30, 40, 50];

    // iter() Returns an iterator,Does not consume vectors
    let mut iter = numbers.iter();

    // next() Every time it returns Option<&T>
    // Some(&value) Indicates that there is another element
    // None Indicates the end of the iteration
    println!("{:?}", iter.next());  // Some(10)
    println!("{:?}", iter.next());  // Some(20)
    println!("{:?}", iter.next());  // Some(30)
    println!("{:?}", iter.next());  // Some(40)
    println!("{:?}", iter.next());  // Some(50)
    println!("{:?}", iter.next());  // None
    println!("{:?}", iter.next());  // None (A subsequent call still returns None)

    // for A loop is next() syntactic sugar
    let mut count = 0;
    let iter2 = numbers.iter();
    for val in iter2 {
        println!("for loop #{}: {}", count, val);
        count += 1;
    }
}

الناتج:

TEXT 📖 للعرض فقط
Some(10)
Some(20)
Some(30)
Some(40)
Some(50)
None
None
for loop #0: 10
for loop #1: 20
for loop #2: 30
for loop #3: 40
for loop #4: 50

العنصر الأساسي في أي مُكرر هو الأسلوب next(): فكل استدعاء له يُرجع Some(element)، وعندما ينفد، يُرجع None. أما الحلقة for فهي مجرد «تسهيل لغوي» يستدعي next() بشكل متكرر حتى يصادف None. وفهم هذا يعني أنك تفهم أساسيات جميع المُكررات.


(2) ▶ المثال:تسلسل محولات المكرر — map / filter / take / skip (مستوى الصعوبة: ⭐⭐)

RUST
// ============================================
// Combine Multiple Adapters to Build a Data Processing Pipeline
// ============================================

fn main() {
    // Raw Data: 1 to 20
    let data = 1..=20;

    // Pipeline: Filter for Even Numbers → multiply by 3 → Skip the first 2 → Take the first 5
    let result: Vec<i32> = data
        .filter(|&n| n % 2 == 0)      // [2,4,6,8,10,12,14,16,18,20]
        .map(|n| n * 3)                // [6,12,18,24,30,36,42,48,54,60]
        .skip(2)                       // [18,24,30,36,42]
        .take(5)                       // [18,24,30,36,42]
        .collect();                    // Triggered Evaluation,Collected Vec

    println!("Pipeline result: {:?}", result);

    // Another pipeline: use chain to Concat two slices
    let first = vec!["A", "B", "C"];
    let second = vec!["X", "Y", "Z"];
    let combined: Vec<&str> = first.iter()
        .chain(second.iter())
        .copied()
        .collect();
    println!("Chained: {:?}", combined);

    // Use enumerate to index elements
    let fruits = vec!["apple", "banana", "cherry"];
    let indexed: Vec<(usize, &str)> = fruits.iter()
        .enumerate()
        .map(|(i, &name)| (i + 1, name))
        .collect();
    println!("Indexed: {:?}", indexed);
}

الناتج:

TEXT 📖 للعرض فقط
Pipeline result: [18, 24, 30, 36, 42]
Chained: ["A", "B", "C", "X", "Y", "Z"]
Indexed: [(1, "apple"), (2, "banana"), (3, "cherry")]

تشبه سلسلة المحولات ترتيب محطات العمل على خط التجميع: فكل محول يقوم بمهمة واحدة فقط، وتتدفق البيانات بالتسلسل عبر كل محطة عمل. collect() هي إشارة الطلب في نهاية خط الأنابيب — وبدونها، لن يبدأ العاملون في خط الأنابيب العمل (التقييم المتأخر).


(3) ▶ المثال:المكررات المخصصة — تنفيذ السمة Iterator لنوعك (مستوى الصعوبة: ⭐⭐)

RUST
// ============================================
// Custom Fibonacci Iterator
// Implementation Iterator trait Make any type iterable
// ============================================

// Fibonacci Sequence Generator
struct Fibonacci {
    current: u64,
    next: u64,
    max: u64,
}

impl Fibonacci {
    fn new(max: u64) -> Self {
        Fibonacci {
            current: 0,
            next: 1,
            max,
        }
    }
}

// Implementation Iterator trait It is the core of custom iterators.
impl Iterator for Fibonacci {
    // Item The type of the elements returned by the iterator
    type Item = u64;

    // next() Back Option<Self::Item>
    // Some(value) Indicates that there is another element
    // None Indicates the end of the iteration
    fn next(&mut self) -> Option<Self::Item> {
        if self.current > self.max {
            return None;
        }
        let result = self.current;

        // Update to the next Fibonacci number
        let new_next = self.current + self.next;
        self.current = self.next;
        self.next = new_next;

        Some(result)
    }
}

fn main() {
    println!("Fibonacci up to 50:");
    let fib = Fibonacci::new(50);

    // Fibonacci It can now be used in for In a loop
    for (i, n) in fib.enumerate() {
        println!("  fib({}) = {}", i, n);
    }

    // It can also be used in conjunction with an adapter chain
    println!("\nEven Fibonacci numbers up to 100:");
    let even_fibs: Vec<u64> = Fibonacci::new(100)
        .filter(|&n| n % 2 == 0)
        .collect();
    println!("{:?}", even_fibs);
}

الناتج:

TEXT 📖 للعرض فقط
Fibonacci up to 50:
  fib(0) = 0
  fib(1) = 1
  fib(2) = 1
  fib(3) = 2
  fib(4) = 3
  fib(5) = 5
  fib(6) = 8
  fib(7) = 13
  fib(8) = 21
  fib(9) = 34

Even Fibonacci numbers up to 100:
[0, 2, 8, 34]

لتنفيذ Iterator trait، ما عليك سوى القيام بأمر واحد: تعريف type Item (نوع العنصر) وfn next() (قاعدة الإنتاج). بمجرد التنفيذ، يكتسب النوع الخاص بك تلقائيًا جميع طرق المُهايئ (map، filter، take، إلخ) — وهذا هو تجسيد لمفهوم «الكتابة حسب النوع» (duck typing) في لغة Rust: إذا قمت بتنفيذ next()، فيمكن استخدامه تمامًا مثل المُكرر.


(4) ▶ المثال:المستهلك في الواقع — الطي / الجمع / العد / التجميع (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Consumer:Drive the iterator to execute and produce the final result
// ============================================

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

    // sum() — Sum
    let total: i32 = numbers.iter().sum();
    println!("Sum: {}", total);

    // count() — Count
    let cnt = numbers.iter().count();
    println!("Count: {}", cnt);

    // fold() — General Folding Operation (initial value + accumulator closure)
    // Here, we calculate 10!
    let factorial: u64 = (1..=10u64).fold(1, |acc, x| acc * x);
    println!("10! = {}", factorial);

    // fold() — Manual Implementation of sum and count
    let sum_via_fold: i32 = numbers.iter().fold(0, |acc, &x| acc + x);
    let count_via_fold: usize = numbers.iter().fold(0, |acc, _| acc + 1);
    println!("Sum (via fold): {}", sum_via_fold);
    println!("Count (via fold): {}", count_via_fold);

    // collect() — Collected various types of collections
    let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
    println!("Doubled: {:?}", doubled);

    let even_set: std::collections::HashSet<i32> = numbers.iter()
        .filter(|&&x| x % 2 == 0)
        .copied()
        .collect();
    println!("Even set: {:?}", even_set);

    // General: find the sum of the first 5 even squares
    let complex_result: i32 = (1..=100)
        .filter(|&n| n % 2 == 0)
        .map(|n| n * n)
        .take(5)
        .fold(0, |acc, n| acc + n);
    println!("Sum of first 5 even squares: {}", complex_result);
}

الناتج:

TEXT 📖 للعرض فقط
Sum: 55
Count: 10
10! = 3628800
Sum (via fold): 55
Count (via fold): 10
Doubled: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Even set: {2, 4, 6, 8, 10}
Sum of first 5 even squares: 220

يُعد المستهلكون العناصر الأساسية في نهاية مسار المعالجة: حيث تقوم sum() وcount() وfold() بحساب النتائج العددية مباشرةً؛ بينما تقوم collect() بجمع البيانات في مجموعة. fold() هو المستهلك الأكثر عموميةً — sum() وcount() هما في الأساس أشكال متخصصة من fold(). يجب أن يحتوي المسار على مستهلك لتنفيذه فعليًّا؛ وإلا فإن الأمر يبقى مجرد كلام.


(5) ▶ المثال:تمرين شامل — تنفيذ مُكرر مخصص لإنشاء مُولِّد لـ FizzBuzz (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example:Custom Iterators + Adapter Chain
// ============================================

struct FizzBuzz {
    current: u32,
    limit: u32,
}

impl FizzBuzz {
    fn new(limit: u32) -> Self {
        FizzBuzz { current: 0, limit }
    }
}

impl Iterator for FizzBuzz {
    type Item = String;

    fn next(&mut self) -> Option<String> {
        self.current += 1;
        if self.current > self.limit {
            return None;
        }
        let n = self.current;
        let result = match (n % 3, n % 5) {
            (0, 0) => "FizzBuzz".to_string(),
            (0, _) => "Fizz".to_string(),
            (_, 0) => "Buzz".to_string(),
            _ => n.to_string(),
        };
        Some(result)
    }
}

struct Fibonacci {
    curr: u64,
    next: u64,
}

impl Iterator for Fibonacci {
    type Item = u64;
    fn next(&mut self) -> Option<u64> {
        let result = self.curr;
        self.curr = self.next;
        self.next = result + self.next;
        Some(result)
    }
}

fn main() {
    println!("=== FizzBuzz (1-20) ===");
    for item in FizzBuzz::new(20) {
        print!("{} ", item);
    }
    println!();

    let fizz_count = FizzBuzz::new(100)
        .filter(|s| s.starts_with("Fizz"))
        .count();
    println!("1-100 Fizz occurrences: {}", fizz_count);

    println!("\n=== Fibonacci first 15 terms ===");
    let fib = Fibonacci { curr: 0, next: 1 };
    for val in fib.take(15) {
        print!("{} ", val);
    }
    println!();

    let fib_sum: u64 = Fibonacci { curr: 1, next: 1 }
        .take_while(|&x| x < 1_000_000)
        .filter(|&x| x % 2 == 0)
        .sum();
    println!("Fibonacci < 1M sum of even numbers: {}", fib_sum);
}

الناتج:

TEXT 📖 للعرض فقط
=== FizzBuzz (1-20) ===
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz 

1-100 Fizz occurrences: 27

=== Fibonacci first 15 terms ===
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 

Fibonacci < 1M sum of even numbers: 1089154

لإنشاء مُكرر مخصص، ما عليك سوى تنفيذ طريقة next() الخاصة بالسمة Iterator. FizzBuzz يمكن ربط المولدات باستخدام filter/count؛ Fibonacci يمكن للمكررات اللانهائية تقييد مخرجاتها باستخدام take/take_while، ثم تجميع النتائج باستخدام filter/sum.



❓ أسئلة شائعة

س ما الفرق بين iter() وinto_iter() وiter_mut()؟
ج إنها تُرجع أنواعًا مختلفة من المُكرِرات. تُرجع iter() &T (مرجع غير قابل للتغيير) دون نقل الملكية؛ تُرجع into_iter() T (نقل الملكية)، مما يؤدي إلى استهلاك المجموعة الأصلية؛ وتُرجع iter_mut() &mut T (مرجع قابل للتعديل)، مما يسمح بتعديل العناصر. تستخدم حلقة for into_iter() بشكل افتراضي.
س لماذا لا تُصدر سلسلة المُحَوِّلات أي خطأ رغم أنها لا تنفذ أي كود؟
ج لأن المُحَوِّلات تعمل بشكل «كسول». فالـ«المُحَوِّلات» تكتفي بوضع «خطة عملية» دون تنفيذها. الأمر أشبه بوجود جميع الآلات معدة في مصنع ولكنها غير مشغلة — فهي لا تبدأ في العمل إلا عند استدعاء مستهلك (collect()، sum()، إلخ). هذه ميزة أساسية في تصميم المكرر في Rust: تجريد بدون أي عبء إضافي لا يُجرى الحساب إلا عند الحاجة الفعلية إليه.
س collect() كيف أعرف النوع الذي يجب التجميع عليه؟
ج من خلال استدلال النوع. عليك تحديد النوع المستهدف، وعادةً ما يتم ذلك باستخدام صيغة TurboFish: .collect::<Vec<i32>>() أو عن طريق إعلان نوع المتغير let v: Vec<i32> = iter.collect();. ويحدد المُجمِّع كيفية التجميع بناءً على تنفيذ النوع المستهدف FromIterator.
س ما المقصود بـ type Item في مكرر مخصص؟
ج Item هو نوع مرتبط يحدد نوع Some الذي تُرجعه next(). على سبيل المثال، يشير type Item = u64 في Iterator for Fibonacci إلى أن next() يُرجع Option<u64> في كل مرة. تتيح لك الأنواع المرتبطة تحديد نوع العناصر التي ينتجها المكرر دون الحاجة إلى معلمات عامة إضافية.
س ما الفرق بين fold() وreduce()؟
ج تتطلب fold() قيمة أولية، بينما تستخدم reduce() العنصر الأول كقيمة أولية. تبدأ fold(0, \|acc, x\| acc + x) العد من 0؛ بينما تبدأ reduce(\|acc, x\| acc + x) العد من العنصر الأول. fold() يُرجع دائمًا نوع القيمة الأولية التي تحددها، بينما reduce() يُرجع Option<Self::Item> (أو None إذا كان المكرر فارغًا).

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: أعد كتابة الكود التالي باستخدام المكررات. استخرج جميع الأعداد الفردية من [1, 2, 3, 4, 5, 6, 7, 8]، واضربها في 10، واحفظها في متجه Vec، ثم اطبعها.

    RUST
    // Replace this loop with a chain of iterators
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8];
    let mut result = vec![];
    for &n in &numbers {
        if n % 2 == 1 {
            result.push(n * 10);
        }
    }
    println!("{:?}", result);
    
  2. الصعوبة ⭐⭐: قم بتطبيق السمة Iterator على struct StepRange { start: i32, end: i32, step: i32 } بحيث يمكن استخدامها مثل for n in StepRange::new(0, 10, 2)، بحيث تُنتج القيم 0، 2، 4، 6، 8، 10. ثم استخدم map لرفع كل قيمة إلى القوة الثانية، واستخدم collect لتجميعها في متجه (Vec).

  3. الصعوبة ⭐⭐⭐: اكتب دالة fn word_count(text: &str) -> std::collections::HashMap<String, usize> تستخدم أساليب المُكرر لحساب عدد مرات ظهور كل كلمة في نص ما. المتطلبات: استخدم split_whitespace() لتقسيم النص، وmap لتحويل الأحرف إلى أحرف صغيرة، وfold لإنشاء HashMap. تلميح: واجهة برمجة التطبيقات entry() الخاصة بـ HashMap، عند استخدامها مع or_insert()، تجعل عملية العد سهلة.

Web-Tutorial.com

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

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

100%