Rust: مكررات Rust: مسارات معالجة البيانات المؤجلة
آخر تحديث: 2026-08-26
المُكرر هو «مسار معالجة البيانات المؤجلة» في لغة Rust — فهو لا يحسب النتائج على الفور، بل يُنتج العناصر واحدة تلو الأخرى، مما يتيح لك معالجة تسلسلات البيانات بطريقة إعلانية من خلال سلسلة من الاستدعاءات.
المكرر يشبه خط التجميع في المصنع: تدخل البيانات من أحد طرفيه، وتمر بسلسلة من العمليات (التصفية، والتحويل، والاستخراج، والتجميع)، ثم تُنتج في النهاية كمنتج نهائي من الطرف الآخر. لا تقوم كل عملية إلا بشيء واحد، ولكن عند دمجها معًا، يمكنها إنجاز مهام معالجة معقدة.
1. قصة مصنع يعمل بنظام خط التجميع
(1) الصعوبة: معالجة البيانات باستخدام الحلقات عملية مملة وتستغرق وقتًا طويلاً
شياو مينغ هو مشرف خط التجميع في مصنع «راست». وهو بحاجة إلى معالجة مجموعة من بيانات قطع الغيار:
- فرز جميع المنتجات المؤهلة (القطع ذات الأرقام الزوجية)
- ضع علامة على كل جزء مرتين
- اختر أول 5 فقط
- المجاميع الإحصائية
وقد كتبها باستخدام حلقة for التقليدية:
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) نهج مسار المكرر
أعد كتابة المنطق المذكور أعلاه باستخدام استدعاءات متسلسلة للمكرر:
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():
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. ما ستتعلمه
- سمة المكرر (Iterator) وطريقة
next: العقد الأساسي للمكرر؛ فهم كيفية قيامnext()بإنتاج العناصر واحدة تلو الأخرى - محولات المكرر:
map/filter/take/skip/chainوطرق تحويل أخرى - المستهلك:
collect/sum/count/fold، إلخ — الطرق التي تدفع المكرر إلى التنفيذ - التقييم المؤجل: لا يتم تنفيذ المُهايئ على الفور؛ بل ينتظر حتى يتم استدعاء المستهلك.
- المكررات المخصصة: قم بتنفيذ
Iterator traitلأنواعك الخاصة - دليل عملي للمكالمات المتسلسلة: الجمع بين المُحَوِّلات والمستهلكين لإتمام مهام معالجة البيانات
4. المفاهيم الأساسية
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() — فهم جوهر المُكرِّرات (مستوى الصعوبة ⭐)
// ============================================
// 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;
}
}
الناتج:
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 (مستوى الصعوبة: ⭐⭐)
// ============================================
// 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);
}
الناتج:
Pipeline result: [18, 24, 30, 36, 42]
Chained: ["A", "B", "C", "X", "Y", "Z"]
Indexed: [(1, "apple"), (2, "banana"), (3, "cherry")]
تشبه سلسلة المحولات ترتيب محطات العمل على خط التجميع: فكل محول يقوم بمهمة واحدة فقط، وتتدفق البيانات بالتسلسل عبر كل محطة عمل.
collect()هي إشارة الطلب في نهاية خط الأنابيب — وبدونها، لن يبدأ العاملون في خط الأنابيب العمل (التقييم المتأخر).
(3) ▶ المثال:المكررات المخصصة — تنفيذ السمة Iterator لنوعك (مستوى الصعوبة: ⭐⭐)
// ============================================
// 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);
}
الناتج:
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) ▶ المثال:المستهلك في الواقع — الطي / الجمع / العد / التجميع (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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);
}
الناتج:
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 (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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);
}
الناتج:
=== 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() كيف أعرف النوع الذي يجب التجميع عليه؟.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 إذا كان المكرر فارغًا).📖 ملخص
- تُعد سمة Iterator أساس نظام المكرر في لغة Rust؛ ولا يتعين عليك سوى تنفيذ الطريقة
next()، التي تُرجعOption<Self::Item>. - محولات المكرر (
map،filter،take،skip،chain) هي محولات «كسولة» — فهي تسجل العمليات فقط ولا تُجري أي حسابات. - المستهلكون (
collect،sum،count،fold) هم النقاط النهائية في مسار المعالجة — ويؤدي استدعاؤهم إلى بدء عملية الحساب الفعلية - المكالمات القابلة للتسلسل تُحوّل كود معالجة البيانات من أسلوب «كيفية التنفيذ» (الأمر) إلى أسلوب «ما تريده» (الإعلان)، مما يجعله أكثر وضوحًا وأكثر قابلية للتركيب
- المكررات المخصصة لا تحتاج سوى إلى تنفيذ
Iterator trait؛ وبمجرد تنفيذها، تصبح قادرة تلقائيًا على الوصول إلى جميع أساليب المُهايئ. - التقييم المتأخر هو مبدأ أساسي في تصميم المُكرر — وهو تجريد خالٍ من الأعباء الإضافية يحسب القيم فقط عند الحاجة، مما يتجنب عمليات التخصيص الوسيطة غير الضرورية.
📝 تمارين
-
الصعوبة ⭐: أعد كتابة الكود التالي باستخدام المكررات. استخرج جميع الأعداد الفردية من
[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); -
الصعوبة ⭐⭐: قم بتطبيق السمة
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). -
الصعوبة ⭐⭐⭐: اكتب دالة
fn word_count(text: &str) -> std::collections::HashMap<String, usize>تستخدم أساليب المُكرر لحساب عدد مرات ظهور كل كلمة في نص ما. المتطلبات: استخدمsplit_whitespace()لتقسيم النص، وmapلتحويل الأحرف إلى أحرف صغيرة، وfoldلإنشاء HashMap. تلميح: واجهة برمجة التطبيقاتentry()الخاصة بـHashMap، عند استخدامها معor_insert()، تجعل عملية العد سهلة.