Rust: المصفوفات والتوبلات في لغة Rust

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

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

في برمجة الأنظمة، تعني المجموعات ذات الحجم الثابت تخطيطًا واضحًا للذاكرة وأداءً يمكن التنبؤ به. يحتاج مُترجم لغة Rust إلى معرفة مقدار الذاكرة التي تشغلها كل متغير، ويتم تحديد أحجام المصفوفات والمجموعات الثلاثية بشكل كامل في وقت الترجمة.


1. ما ستتعلمه



2. قصة محلل مالي

(1) المشكلة: تخزين البيانات في متغيرات متباينة

أليس هي محللة مالية في الشركة، وتحتاج إلى تخزين بيانات الإيرادات الشهرية للنصف الأول من عام 2026:

RUST
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

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) مقارنة بين المفاهيم

100%
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) ▶ المثال:إعلان المصفوفات والوصول إليها والتكرار فيها (مستوى الصعوبة ⭐)

RUST
// ============================================
// 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);
}

الناتج:

TEXT 📖 للعرض فقط
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) ▶ المثال:تجاوز حدود مؤشر المصفوفة وفحوصات الأمان (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// 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
}

الناتج:

TEXT 📖 للعرض فقط
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) ▶ المثال:إنشاء وفك حزم العناصر، وقيم إرجاع الدوال (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// 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);
}

الناتج:

TEXT 📖 للعرض فقط
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) ▶ المثال:التكرار عبر المصفوفات والطرق الشائعة (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// 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());
}

الناتج:

TEXT 📖 للعرض فقط
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) ▶ المثال:تمرين شامل — إحصائيات درجات الطلاب (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// 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]);
}

الناتج:

TEXT 📖 للعرض فقط
=== 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()].


📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: قم بتعريف مصفوفة [f64; 7] بطول 7 لتخزين أعلى درجات الحرارة (بالدرجات المئوية) لكل يوم من أيام الأسبوع السبعة. استخدم حلقة لتحديد أعلى وأدنى درجات الحرارة.
  2. الصعوبة ⭐⭐: اكتب دالة fn stats(arr: &[i32]) -> (i32, i32, f64) تُرجع (الحد الأدنى، والحد الأقصى، والمتوسط). اختبرها في الدالة الرئيسية باستخدام [10, 3, 7, 1, 9, 4].
  3. الصعوبة ⭐⭐⭐: عرّف مصفوفة لتخزين درجات الطلاب [85, 92, 78, 90, 88]. قم بتنفيذ دالة يدويًّا fn rank_scores(scores: &[i32]) -> Vec<(usize, i32, &str)> تُرجع قائمة من المجموعات المُرتبة (الترتيب، الدرجة، التقدير) (قواعد التقدير: >=90 تعني "A"، >=80 تعني "B"، >=70 تعني "C"، وجميع الدرجات الأخرى تعني "D").
Web-Tutorial.com

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

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

100%