Rust: المصفوفات والتوبلات في لغة Rust
آخر تحديث: 2026-08-26
المصفوفات والتوبلات هما «المجموعات ذات الحجم الثابت» الأساسية في لغة Rust — حيث تخزن المصفوفات قيمًا متعددة من نفس النوع، بينما يمكن للتوبلات تخزين قيم متعددة من أنواع مختلفة. ويتم تخصيص الذاكرة لكليهما على المكدس ولا تتسبب في أي عبء إضافي أثناء وقت التشغيل.
في برمجة الأنظمة، تعني المجموعات ذات الحجم الثابت تخطيطًا واضحًا للذاكرة وأداءً يمكن التنبؤ به. يحتاج مُترجم لغة Rust إلى معرفة مقدار الذاكرة التي تشغلها كل متغير، ويتم تحديد أحجام المصفوفات والمجموعات الثلاثية بشكل كامل في وقت الترجمة.
1. ما ستتعلمه
- قواعد بناء الجمل الخاصة بتعريف المصفوفات وتهيئتها
[T; N] - الوصول إلى عناصر المصفوفة باستخدام الفهرس وفحوصات تجاوز الحدود
- إنشاء المجموعات (Tuples) وتفكيك الأنماط
- استخدام المجموعات (tuples) لتنفيذ الدوال ذات القيم المرجعة المتعددة
- طرق التكرار على المصفوفات (حلقات for، iter، enumerate)
2. قصة محلل مالي
(1) المشكلة: تخزين البيانات في متغيرات متباينة
أليس هي محللة مالية في الشركة، وتحتاج إلى تخزين بيانات الإيرادات الشهرية للنصف الأول من عام 2026:
let jan = 120_000; // Jan
let feb = 135_000; // Feb
let mar = 128_000; // Mar
let apr = 142_000; // Apr
let may = 150_000; // May
let jun = 165_000; // Jun
// Want to calculate half-year total?
let sum = jan + feb + mar + apr + may + jun; // Tedious and error-prone
إن التعامل مع بيانات ستة أشهر يمثل عبئًا بالفعل — فماذا لو اضطررت إلى تخزين بيانات 365 يومًا؟ هل سيتعين عليك تعريف 365 متغيرًا؟ إن تمرير ستة متغيرات إلى دالة يكفي بالفعل لدفعك إلى الجنون. والأكثر من ذلك، أنه لا يمكنك استخدام حلقة معالجة للتعامل معها.
(2) حل لمصفوفات Rust
fn main() {
// Half-year revenue: Jan ~ Jun 2026
let revenue: [i32; 6] = [120_000, 135_000, 128_000, 142_000, 150_000, 165_000];
// Total: one line
let sum: i32 = revenue.iter().sum();
println!("2026 H1 total revenue: {} yuan", sum);
// Average
let avg = sum as f64 / revenue.len() as f64;
println!("Monthly average: {:.0} yuan", avg);
// Print each month
for (i, val) in revenue.iter().enumerate() {
println!("Month {}: {} yuan", i + 1, val);
}
}
تُخزّن المصفوفات جميع البيانات من النوع نفسه في متغير واحد — يمكنك الوصول إليها باستخدام المؤشرات، والتكرار عبرها باستخدام الحلقات، وتمريرها إلى الدوال، واستخدام المُكرِّرات لحساب البيانات وتجميعها. ست أسطر من التعليمات البرمجية تحل محل 20 سطراً من المتغيرات المتناثرة.
3. المصفوفات والمجموعات
(1) مقارنة بين المفاهيم
graph TB
A[Rust fixed-size collections] --> B[Array [T; N]]
A --> C[Tuple (T1, T2, ...)]
B --> D[All elements same type]
B --> E[Compile-time fixed length]
B --> F[Index access: arr[i]]
C --> G[Elements can differ in type]
C --> H[Pattern destructuring]
C --> I[Multiple return values]
(2) المصفوفات مقابل المجموعات
| البعد | مصفوفة [T; N] |
مجموعة (T1, T2, ...) |
|---|---|---|
| نوع العنصر | يجب أن تكون جميعها متطابقة | يجوز أن تكون مختلفة |
| الطول | ثابت عند التحويل البرمجي (N) | ثابت عند التحويل البرمجي (عدد العناصر) |
| طريقة الوصول | arr[index] |
tuple.field_index أو التفكيك |
| حالات الاستخدام | مجموعات من البيانات من نفس النوع (مثل: الإيرادات الشهرية) | مجموعات من البيانات غير المتجانسة (مثل: قيم الإرجاع ورموز الأخطاء) |
| الذاكرة | كتلة ذاكرة متجاورة | كتلة ذاكرة متجاورة (قد تتضمن حشوًا للمحاذاة) |
| المعلمات العامة | [T; N] — النوع + الطول |
(T1, T2) — النوع في كل موضع |
| فحص تجاوز الحدود | حالة ذعر في وقت التشغيل | غير متوفر (رقم الحقل معروف في وقت التحويل البرمجي) |
(3) مرجع سريع لأساليب المصفوفات الشائعة
| الطريقة | نوع القيمة المرجعة | الوصف |
|---|---|---|
len() |
usize |
طول المصفوفة المُرجعة |
get(i) |
Option<&T> |
الوصول الآمن إلى الفهرس |
get_mut(i) |
Option<&mut T> |
الوصول الآمن إلى المتغيرات |
iter() |
Iter<T> |
العودة إلى «مكررات المرجع» |
contains(&val) |
bool |
ما إذا كانت القيمة مدرجة أم لا |
sort() |
() |
الفرز في المكان (يتطلب &mut) |
map(f) |
— | تم التحويل عبر .iter().map() |
reverse() () الدوران في المكان (يتطلب &mut) |
(4) اختيار مجموعة ذات طول ثابت
| السيناريو | النوع الموصى به | السبب |
|---|---|---|
| عدد ثابت من العناصر من نفس النوع | [T; N] مصفوفة |
تخصيص في المكدس، بدون عبء إضافي، آمن من حيث النوع |
| التوليفات الثابتة غير المتجانسة | (T1, T2, ...) التوبول |
التوليفات من أنواع مختلفة، تحليل الأنماط |
| الدوال ذات القيم المرتجعة المتعددة | المجموعات | خفيفة الوزن؛ لا حاجة لتعريف الهياكل |
| RGB/الإحداثيات | بنية التوبول | تحتوي على اسم النوع، مما يمنع تضارب الأسماء |
| كميات كبيرة من البيانات المتشابهة | Vec<T> |
النمو الديناميكي، تخصيص الذاكرة في الكومة |
4. أمثلة على المصفوفات والمجموعات
(1) ▶ المثال:إعلان المصفوفات والوصول إليها والتكرار فيها (مستوى الصعوبة ⭐)
// ============================================
// Arrays: declaration, indexing, and iteration
// ============================================
fn main() {
// Type 1: Explicit type annotation
let months: [i32; 6] = [100, 200, 300, 400, 500, 600];
// Type 2: Type inference
let zeros = [0; 5]; // [0, 0, 0, 0, 0], shorthand for [0, 0, 0, 0, 0]
// Access by index (0-based)
println!("First month: {}", months[0]); // 100
println!("Third month: {}", months[2]); // 300
// len() returns the array length
println!("Array length: {}", months.len()); // 6
// Iterate with a for loop
print!("All values: ");
for val in months {
print!("{} ", val);
}
println!();
// Iterate with index using .iter().enumerate()
for (index, value) in months.iter().enumerate() {
println!("months[{}] = {}", index, value);
}
// The shorthand `[val; N]` syntax
println!("Zeros array: {:?}", zeros);
}
الناتج:
First month: 100
Third month: 300
Array length: 6
All values: 100 200 300 400 500 600
months[0] = 100
months[1] = 200
months[2] = 300
months[3] = 400
months[4] = 500
months[5] = 600
Zeros array: [0, 0, 0, 0, 0]
هناك طريقتان لإعلان مصفوفة:
[initial_value; length]هي صيغة مختصرة مريحة، و[type; length]هي الصيغة الكاملة. استخدمarr[i]للوصول إلى العناصر حسب الفهرس، وfor val in arrللتنقل عبر المصفوفة. تتيح لك.iter().enumerate()استرداد كل من الفهرس والقيمة في آن واحد.
(2) ▶ المثال:تجاوز حدود مؤشر المصفوفة وفحوصات الأمان (مستوى الصعوبة ⭐⭐)
// ============================================
// Array out-of-bounds: Rust panics at runtime
// ============================================
fn main() {
let scores: [i32; 3] = [95, 87, 92];
// Safe access: within bounds
println!("scores[0] = {}", scores[0]); // OK
println!("scores[1] = {}", scores[1]); // OK
println!("scores[2] = {}", scores[2]); // OK
// Out of bounds: THIS WILL PANIC at runtime
// Uncomment the line below to see the error:
// println!("scores[5] = {}", scores[5]);
//
// Output:
// thread 'main' panicked at src/main.rs:XX:YY:
// index out of bounds: the len is 3 but the index is 5
// Safe alternative: use .get() which returns Option<&T>
let first = scores.get(0); // Some(&95)
let invalid = scores.get(5); // None
match first {
Some(val) => println!("Safe get(0): {}", val),
None => println!("Index 0 out of bounds"),
}
match invalid {
Some(val) => println!("Safe get(5): {}", val),
None => println!("Index 5 out of bounds -- safely handled!"),
}
// Using .get() with a default value
let val = scores.get(5).copied().unwrap_or(-1);
println!("scores.get(5) with default: {}", val); // -1
}
الناتج:
scores[0] = 95
scores[1] = 87
scores[2] = 92
Safe get(0): 95
Index 5 out of bounds -- safely handled!
scores.get(5) with default: -1
سيؤدي استخدام
arr[i]مباشرةً إلى حدوث حالة ذعر في وقت التشغيل (تعطل) إذا كان المؤشر خارج النطاق. والطريقة الآمنة هي استخدام الطريقة.get()— فهي تُرجعOption<&T>، مما يتيح لك التعامل بسلاسة مع حالات الخروج عن النطاق باستخدامmatchأوunwrap_orبدلاً من التسبب في تعطل فوري.
(3) ▶ المثال:إنشاء وفك حزم العناصر، وقيم إرجاع الدوال (مستوى الصعوبة ⭐⭐)
// ============================================
// Tuples: creation, destructuring, and return values
// ============================================
// A function that returns a tuple: (sum, count, average)
fn analyze_sales(sales: &[i32]) -> (i32, usize, f64) {
let sum: i32 = sales.iter().sum();
let count = sales.len();
let avg = sum as f64 / count as f64;
(sum, count, avg) // return as a tuple
}
fn main() {
// Tuple with different types: (name, age, active)
let person: (&str, u8, bool) = ("Alice", 30, true);
// Access by field index
println!("Name: {}", person.0);
println!("Age: {}", person.1);
println!("Active: {}", person.2);
// Destructuring: unpack tuple into variables
let (name, age, active) = person;
println!("Destructured -- {} is {} years old, active: {}", name, age, active);
// Tuple as function return value
let q1_sales = [120_000, 135_000, 128_000]; // Jan, Feb, Mar
let (sum, count, avg) = analyze_sales(&q1_sales);
println!("Q1 -- Sum: {}, Count: {}, Avg: {:.0}", sum, count, avg);
// Nested tuples
let nested = (1, (2.5, "hello"), true);
println!("Nested: {:?}, inner: {}", nested, (nested.1).1);
// Single-element tuple: note the trailing comma!
let single = (42,); // tuple with one element
let not_tuple = (42); // just a parenthesized integer
println!("Single-element tuple: {:?}", single);
println!("Not a tuple: {}", not_tuple);
}
الناتج:
Name: Alice
Age: 30
Active: true
Destructured -- Alice is 30 years old, active: true
Q1 -- Sum: 383000, Count: 3, Avg: 127667
Nested: (1, (2.5, "hello"), true), inner: hello
Single-element tuple: (42,)
Not a tuple: 42
يمكن للتوبلات تخزين قيم من أنواع مختلفة. يمكنك الوصول إلى حقولها باستخدام
.0و.1و.2، أو استخدام النمطlet (a, b, c) = tupleلإزالة البنية. وتُعد التوبلات مناسبة بشكل خاص للدوال التي تُرجع قيمًا متعددة — دون الحاجة إلى تعريف بنية. لاحظ أن التوبول المكون من عنصر واحد يجب أن يُحاط بفاصلة(42,).
(4) ▶ المثال:التكرار عبر المصفوفات والطرق الشائعة (مستوى الصعوبة ⭐⭐)
// ============================================
// Array iteration and common methods
// ============================================
fn main() {
let revenue: [i32; 6] = [120_000, 135_000, 128_000, 142_000, 150_000, 165_000];
let month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
// Method 1: for-in (by value -- copies each element for i32)
print!("All revenues: ");
for r in revenue {
print!("{} ", r);
}
println!();
// Method 2: .iter() (by reference)
print!("With .iter(): ");
for r in revenue.iter() {
print!("{} ", r);
}
println!();
// Method 3: .iter().enumerate() (index + value)
println!("\nMonthly report:");
for (i, r) in revenue.iter().enumerate() {
println!(" {}: {} yuan", month_names[i], r);
}
// Method 4: for i in 0..len (C-style index)
println!("\nGrowth rate:");
for i in 1..revenue.len() {
let growth = (revenue[i] - revenue[i - 1]) as f64 / revenue[i - 1] as f64 * 100.0;
println!(" {} -> {}: {:.1}%", month_names[i - 1], month_names[i], growth);
}
// Common array methods
println!("\nSummary:");
println!(" Total: {}", revenue.iter().sum::<i32>());
println!(" Max: {}", revenue.iter().max().unwrap());
println!(" Min: {}", revenue.iter().min().unwrap());
println!(" Count > 140k: {}", revenue.iter().filter(|&&r| r > 140_000).count());
}
الناتج:
All revenues: 120000 135000 128000 142000 150000 165000
With .iter(): 120000 135000 128000 142000 150000 165000
Monthly report:
Jan: 120000 yuan
Feb: 135000 yuan
Mar: 128000 yuan
Apr: 142000 yuan
May: 150000 yuan
Jun: 165000 yuan
Growth rate:
Jan -> Feb: 12.5%
Feb -> Mar: -5.2%
Mar -> Apr: 10.9%
Apr -> May: 5.6%
May -> Jun: 10.0%
Summary:
Total: 840000
Max: 165000
Min: 120000
Count > 140k: 3
هناك عدة طرق للتكرار عبر مصفوفة:
for val in arr(نسخ القيم)،arr.iter()(بالمرجع)،.enumerate()(مفهرسة)، وحلقات التكرار المفهرسة على غرار لغة C. كما توفر المصفوفات مجموعة غنية من أساليب التكرار، مثل.sum()و.max()و.min()و.filter().
(5) ▶ المثال:تمرين شامل — إحصائيات درجات الطلاب (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// Comprehensive Example: Array + Tuple + Iterators in Practice
// ============================================
fn analyze(scores: &[i32]) -> (i32, i32, f64, i32) {
let min = *scores.iter().min().unwrap_or(&0);
let max = *scores.iter().max().unwrap_or(&0);
let sum: i32 = scores.iter().sum();
let avg = if scores.is_empty() { 0.0 } else { sum as f64 / scores.len() as f64 };
let above_avg = scores.iter().filter(|&&s| s as f64 >= avg).count() as i32;
(min, max, avg, above_avg)
}
fn classify(score: i32) -> &'static str {
match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
_ => "F",
}
}
fn main() {
let students = [
("Alice", [95, 88, 92]),
("Bob", [72, 65, 58]),
("Charlie", [85, 90, 78]),
("David", [60, 55, 70]),
];
println!("=== Student Report Card ===");
println!("{:<10} {:>6} {:>6} {:>6} {:>8} {:>6}", "Name", "Sub1", "Sub2", "Sub3", "Average", "Level");
println!("{}", "-".repeat(44));
let mut all_scores: Vec<i32> = Vec::new();
for (name, scores) in &students {
let (min, max, avg, above) = analyze(scores);
let grade = classify(avg as i32);
println!("{:<10} {:>6} {:>6} {:>6} {:>8.1} {:>6}",
name, scores[0], scores[1], scores[2], avg, grade);
all_scores.extend(scores.iter());
}
let (overall_min, overall_max, overall_avg, above_count) = analyze(&all_scores);
println!("{}", "-".repeat(44));
println!("Class-wide Statistics: Lowest={}, Highest={}, Average={:.1}, >=Average: {} people",
overall_min, overall_max, overall_avg, above_count);
let subject_avgs: [f64; 3] = [
students.iter().map(|(_, s)| s[0] as f64).sum::<f64>() / students.len() as f64,
students.iter().map(|(_, s)| s[1] as f64).sum::<f64>() / students.len() as f64,
students.iter().map(|(_, s)| s[2] as f64).sum::<f64>() / students.len() as f64,
];
println!("\nSubject Average: Sub1={:.1}, Sub2={:.1}, Sub3={:.1}",
subject_avgs[0], subject_avgs[1], subject_avgs[2]);
}
الناتج:
=== Student Report Card ===
Name Sub1 Sub2 Sub3 Average Level
--------------------------------------------
Alice 95 88 92 91.7 A
Bob 72 65 58 65.0 D
Charlie 85 90 78 84.3 B
David 60 55 70 61.7 D
--------------------------------------------
Class-wide Statistics: Lowest=55, Highest=95, Average=76.7, >=Average: 6 people
Subject Average: Sub1=78.0, Sub2=74.5, Sub3=74.5
يجمع هذا المثال بين استخدام المصفوفات والمجموعات:
[i32; 3]يخزن درجات كل طالب في ثلاث مواد دراسية؛ والمجموعة(&str, [i32; 3])تجمع بين الأسماء والدرجات؛ وanalyzeيُرجع المجموعة(min, max, avg, count)؛ و[f64; 3]يخزن متوسط الدرجات لكل مادة دراسية.
❓ أسئلة شائعة
س: ما الفرق بين المصفوفة
[u8; 3]والشريحة&[u8]؟ ج: يتم تحديد حجم المصفوفة في وقت التحويل البرمجي وتخصيصها في المكدس؛ أما الشريحة فهي عرض لمصفوفة (مؤشر + طول) يمكن أن تشير إلى جزء من المصفوفة. المصفوفة هي[T; N](حيث N ثابت في وقت التحويل البرمجي)، بينما الشريحة هي&[T](حيث يتم تحديد الطول في وقت التشغيل). وتكون الشرائح عمومًا أكثر مرونة عند استخدامها كمعلمات للدوال.
س: ما هو الحد الأقصى لعدد العناصر التي يمكن أن تحتوي عليها التوبلة؟ ج: من الناحية النظرية، يمكن أن تحتوي على ما يصل إلى 12 عنصرًا (تنفذ المكتبة القياسية سمات للتوبلات التي تحتوي على ما يصل إلى 12 عنصرًا). ومع ذلك، إذا كان لديك أكثر من 4 أو 5 حقول، يُنصح باستخدام بنية (struct).
س: لماذا لا يُبلغ المُترجم عن خطأ عندما يتجاوز المصفوفة حدودها، لكنه يُصدر استثناءً في وقت التشغيل؟ ج: لا يستطيع المُترجم التحقق إلا من حالات الوصول خارج الحدود التي يمكن تحديدها في وقت الترجمة (مثل
arr[100]حيث يكون طول المصفوفة ثابتًا أيضًا). أما الفهرسة الديناميكية (مثلarr[i]حيث يأتيiمن إدخال المستخدم) فلا يمكن التحقق منها إلا أثناء وقت التشغيل. يُعد استخدام.get()طريقة آمنة للتعامل مع هذه الحالة.
س: تُستخدم كل من التوبلات (tuples) والبنى (structs) لـ«تجميع قيم متعددة» — فمتى يجب استخدام أي منهما؟ ج: تُعد التوبلات مناسبة للحالات المؤقتة التي لا تحمل فيها الحقول أسماءً ذات دلالة (مثل قيم الإرجاع المتعددة)؛ أما البنى فهي مناسبة للبيانات التي تحمل حقولها معاني واضحة وتحتاج إلى إعادة الاستخدام. قاعدة عامة جيدة: إذا كان هناك أكثر من ثلاثة حقول، أو إذا ظهر نفس نوع التوبول عدة مرات في الكود الخاص بك، فانتقل إلى استخدام البنية.
س:
[0; 5]هل تعمل صيغة الكتابة هذه مع جميع الأنواع؟ ج: يجب أن يُنفِّذ نوع العنصر السمةCopy. على سبيل المثال، لا يمكن كتابة[String; 3]بهذه الطريقة (لأن String ليس نوعًا من أنواع Copy). في هذه الحالة، يجب تهيئة كل عنصر على حدة:["a".into(), "b".into(), "c".into()].
📖 ملخص
- المصفوفة
[T; N]هي مجموعة من العناصر من نفس النوع وذات طول ثابت؛ ويكون حجمها معروفًا في وقت التحويل البرمجي، ويتم تخصيصها في المكدس. - يتم الوصول إلى المصفوفات عبر الفهرس
arr[i]؛ ويؤدي الوصول خارج نطاق المصفوفة إلى حدوث حالة ذعر أثناء التشغيل؛ وتُرجع الطريقة.get()القيمةOptionلضمان المعالجة الآمنة - الصيغة المختصرة للمصفوفات
[val; N]إنشاء N عنصرًا بنفس القيمة (يتطلب T: نسخ) - المجموعة
(T1, T2)يمكنها تخزين أنواع مختلفة، ويمكن الوصول إليها عبر.0/.1أو عن طريق تحليل الأنماط. - تُعد المجموعات (Tuples) خيارًا مثاليًّا للدوال ذات القيم المرجعة المتعددة—
fn foo() -> (i32, String) - الاختيار بين المجموعات (Tuples) والهياكل (Structures): استخدم المجموعات للبيانات المؤقتة، والهياكل للحقول ذات المعاني المحددة
📝 تمارين
- الصعوبة ⭐: قم بتعريف مصفوفة
[f64; 7]بطول 7 لتخزين أعلى درجات الحرارة (بالدرجات المئوية) لكل يوم من أيام الأسبوع السبعة. استخدم حلقة لتحديد أعلى وأدنى درجات الحرارة. - الصعوبة ⭐⭐: اكتب دالة
fn stats(arr: &[i32]) -> (i32, i32, f64)تُرجع (الحد الأدنى، والحد الأقصى، والمتوسط). اختبرها في الدالة الرئيسية باستخدام[10, 3, 7, 1, 9, 4]. - الصعوبة ⭐⭐⭐: عرّف مصفوفة لتخزين درجات الطلاب
[85, 92, 78, 90, 88]. قم بتنفيذ دالة يدويًّاfn rank_scores(scores: &[i32]) -> Vec<(usize, i32, &str)>تُرجع قائمة من المجموعات المُرتبة (الترتيب، الدرجة، التقدير) (قواعد التقدير: >=90 تعني "A"، >=80 تعني "B"، >=70 تعني "C"، وجميع الدرجات الأخرى تعني "D").