Rust: متجهات Rust (Vec)

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

Vec<T> (vector) هو المصفوفة الديناميكية الأكثر استخدامًا في المكتبة القياسية لـ Rust — حيث يمكن توسيعها أو تقليصها أثناء وقت التشغيل، ويتم تخصيص جميع البيانات في الـ heap. وهي تعادل في Rust ما يُعرف بـ «ArrayList» أو «قائمة Python».

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


1. ما ستتعلمه



2. قصة أمينة صندوق في سوبرماركت

(1) الإحباط: عدم معرفة حجم سلة التسوق

يعمل توم على تطوير نظام للدفع عند الخروج في السوبرماركت. عربة التسوق الخاصة بالعميل:

RUST
// The problem with arrays -- you must know how many items in advance!
let cart: [&str; 3] = ["milk", "bread", "eggs"];

// Customer says: "Add one more cola"
// Arrays can't grow dynamically -- you need to redeclare...
let mut cart2 = ["milk", "bread", "eggs", "cola"];
// Customer: "Remove eggs, add butter instead"
// Modify every time? This is painful!

يتم تحديد حجم المصفوفة في وقت التحويل البرمجي. لكن في حالة عربة التسوق، لا يمكنك معرفة ما إذا كان العميل سيشتري 3 عناصر أم 30. لذا فأنت بحاجة إلى بنية بيانات تتوسع ديناميكيًا أثناء التشغيل.

(2) حل لـ Rust Vec

RUST
fn main() {
    // Start with an empty cart
    let mut cart: Vec<&str> = Vec::new();

    // Customer adds items one by one
    cart.push("milk");
    cart.push("bread");
    cart.push("eggs");
    println!("Cart has {} items: {:?}", cart.len(), cart);

    // Customer adds more
    cart.push("cola");
    println!("Added cola: {:?}", cart);

    // Customer removes an item
    cart.pop();
    println!("Removed last: {:?}", cart);

    // Check what's inside
    println!("Current cart: {:?}", cart);
}

Vec::new() إنشاء مصفوفة ديناميكية فارغة؛ push إضافة عناصر (يتم تغيير حجم المصفوفة تلقائيًا)؛ pop إزالة العنصر الأخير. لا حاجة لتحديد الحجم — حيث تتولى Vec إدارة الذاكرة في الـ«هياب» تلقائيًا.



3. المصفوفة الديناميكية Vec

(1) نظرة عامة على المفهوم

100%
graph LR
    A[Vec&lt;T&gt; dynamic array] --> B[Creation]
    A --> C[CRUD operations]
    A --> D[Capacity management]
    A --> E[Conversion]

    B --> B1[Vec::new()]
    B --> B2[vec! macro]
    B --> B3[collect()]

    C --> C1[push / pop]
    C --> C2[insert / remove]
    C --> C3[index / .get()]

    D --> D1[capacity: allocated]
    D --> D2[len: actual use]
    D --> D3[shrink_to_fit]

    E --> E1[From array]
    E --> E2[Back to array]

(2) مقارنة المصفوفات المتجهة (Vec) والمصفوفات العادية (Arrays)

البعد مصفوفة [T; N] متجه Vec<T>
الحجم ثابت عند التحويل البرمجي يتزايد ديناميكيًا أثناء التشغيل
موقع التخصيص المكدس (عادةً) الكومة
تغيير الحجم غير مدعوم تغيير الحجم تلقائيًا بمقدار 2x
ثراء واجهة برمجة التطبيقات (API) محدود غني للغاية
سرعة الوصول سريعة للغاية (متجاورة في المكدس) سريعة للغاية (متجاورة في الـ«هياب»)
حالات الاستخدام حجم ثابت، يتم تخصيصه في المكدس عدد غير معروف، مع عمليات إضافة وحذف متكررة
الأعباء الإضافية على الأداء لا شيء قد يؤدي push إلى إعادة تخصيص الذاكرة

(3) مرجع سريع لأساليب Vec الشائعة

الطريقة نوع القيمة المرجعة الوصف التعقيد الزمني
push(val) () الإضافة إلى النهاية O(1) متوسطة
pop() Option<T> ظهور الذيل O(1)
insert(idx, val) () الإدراج في موضع محدد O(n)
remove(idx) T الإزالة من موضع محدد O(n)
get(idx) Option<&T> الوصول الآمن O(1)
len() usize عدد العناصر O(1)
capacity() usize السعة المخصصة O(1)
clear() () حل O(n)
contains(&val) bool يحتوي على O(n)
sort() () الفرز في المكان نفسه O(n log n)
dedup() () إزالة التكرارات (يجب فرزها أولاً) O(n)
retain(f) () الاحتفاظ بالعناصر التي تستوفي المعايير O(n)
shrink_to_fit() () تحرير السعة الزائدة

(4) مقارنة بين طرق التكرار المتجهية

الطريقة الصيغة الملكية متوفرة في Vec
تكرار مستعار for x in &v مرجع للقراءة فقط متاح
التصفح المتغير العدد for x in &mut v المراجع المتغيرة العدد متاح (تم إصلاحه)
تصفح الاستهلاك for x in v نقل الملكية غير متوفر
مُكرر v.iter() مرجع للقراءة فقط متاح
التعداد والتكرار v.iter().enumerate() مرجع للقراءة فقط + فهرس متاح


4. مثال

(1) ▶ المثال:الماكرو vec! وpush/pop (مستوى الصعوبة ⭐)

RUST
// ============================================
// Vec: vec! macro, push, pop, len, is_empty
// ============================================

fn main() {
    // Method 1: Vec::new()
    let mut v1: Vec<i32> = Vec::new();
    v1.push(10);
    v1.push(20);
    v1.push(30);
    println!("v1: {:?}, len={}", v1, v1.len());

    // Method 2: vec! macro (most common)
    let mut v2 = vec!["apple", "banana", "cherry"];
    println!("v2: {:?}, len={}", v2, v2.len());

    // Method 3: vec! with repeated value
    let v3 = vec![0; 5];
    println!("v3 (five zeros): {:?}", v3);

    // push: add to the end
    v2.push("date");
    println!("After push: {:?}", v2);

    // pop: remove from the end
    let last = v2.pop();
    println!("Popped: {:?}", last);
    println!("After pop: {:?}", v2);

    // is_empty
    println!("v2 empty? {}", v2.is_empty());

    // Access by index
    println!("v2[0] = {}", v2[0]);

    // len
    println!("v2 length: {}", v2.len());
}

الناتج:

TEXT 📖 للعرض فقط
v1: [10, 20, 30], len=3
v2: ["apple", "banana", "cherry"], len=3
v3 (five zeros): [0, 0, 0, 0, 0]
After push: ["apple", "banana", "cherry", "date"]
Popped: Some("date")
After pop: ["apple", "banana", "cherry"]
v2 empty? false
v2[0] = apple
v2 length: 3

vec! هي الطريقة الأكثر شيوعًا لإنشاء قائمة — vec!["a", "b", "c"] أو vec![0; 5] (5 أصفار). push تضيف عنصرًا إلى نهاية القائمة، وpop تزيل عنصرًا من نهاية القائمة (وتُرجع Option<T>). len() تُرجع عدد العناصر الموجودة في القائمة.


(2) ▶ المثال:الإدراج/الإزالة وإدارة السعة (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Vec: insert, remove, capacity vs length
// ============================================

fn main() {
    let mut cart: Vec<&str> = Vec::with_capacity(3);

    // capacity vs length
    println!("Initial -- len: {}, cap: {}", cart.len(), cart.capacity());

    cart.push("milk");
    cart.push("bread");
    cart.push("eggs");
    println!("After 3 pushes -- len: {}, cap: {}", cart.len(), cart.capacity());

    // insert at arbitrary position
    cart.insert(1, "cola");  // insert "cola" at index 1
    println!("After insert at 1: {:?}", cart);
    println!("Now -- len: {}, cap: {}", cart.len(), cart.capacity());
    // Capacity may have doubled!

    // remove at arbitrary position
    let removed = cart.remove(2);  // remove element at index 2
    println!("Removed: {}", removed);
    println!("After remove at 2: {:?}", cart);

    // get -- safe access (returns Option<&T>)
    match cart.get(0) {
        Some(item) => println!("First item: {}", item),
        None => println!("Empty cart!"),
    }

    // Try an out-of-bounds index with get (safe)
    match cart.get(99) {
        Some(item) => println!("Item at 99: {}", item),
        None => println!("Index 99 is out of bounds!"),
    }

    // shrink_to_fit: reduce capacity to match length
    cart.shrink_to_fit();
    println!("After shrink -- len: {}, cap: {}", cart.len(), cart.capacity());
}

الناتج:

TEXT 📖 للعرض فقط
Initial -- len: 0, cap: 3
After 3 pushes -- len: 3, cap: 3
After insert at 1: ["milk", "cola", "bread", "eggs"]
Now -- len: 4, cap: 6
Removed: bread
After remove at 2: ["milk", "cola", "eggs"]
First item: milk
Index 99 is out of bounds!
After shrink -- len: 3, cap: 3

insert(idx, val) تُدرج في الموضع المحدد (مع إزاحة العناصر اللاحقة إلى اليمين)، وremove(idx) تُزيل العنصر الموجود في الموضع المحدد وتُرجعه. capacity هي مقدار الذاكرة المخصصة لـ Vec (والتي قد تكون أكبر من len)، وshrink_to_fit() تُحرر المساحة الزائدة. .get() يضمن الوصول الآمن؛ وفي حالة الوصول خارج النطاق، فإنه يُرجع None بدلاً من التسبب في حالة ذعر.


(3) ▶ المثال:طرق التجول والتكرار في المصفوفات (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Vec: iteration, mutation, and functional methods
// ============================================

fn main() {
    let mut prices = vec![29.9, 49.9, 15.5, 99.0, 8.8];

    // Method 1: for-in by reference
    print!("Prices: ");
    for p in &prices {
        print!("{:.1} ", p);
    }
    println!();

    // Method 2: mutable iteration (add tax)
    for p in &mut prices {
        *p *= 1.1;  // 10% tax
    }
    println!("With 10% tax: {:?}", prices);

    // Method 3: .iter().enumerate()
    for (i, p) in prices.iter().enumerate() {
        println!("Item {}: {:.2}", i, p);
    }

    // Method 4: functional style -- map, filter, sum
    let total: f64 = prices.iter().sum();
    println!("Total: {:.2}", total);

    let above_30: Vec<f64> = prices.iter()
        .filter(|&&p| p > 30.0)
        .copied()
        .collect();
    println!("Items above 30: {:?}", above_30);

    let discounted: Vec<f64> = prices.iter()
        .map(|p| p * 0.8)  // 20% off
        .collect();
    println!("20% discount: {:?}", discounted);
}

الناتج:

TEXT 📖 للعرض فقط
Prices: 29.9 49.9 15.5 99.0 8.8
With 10% tax: [32.89, 54.89, 17.05, 108.9, 9.68]
Item 0: 32.89
Item 1: 54.89
Item 2: 17.05
Item 3: 108.90
Item 4: 9.68
Total: 223.41
Items above 30: [32.89, 54.89, 108.9]
20% discount: [26.312, 43.912, 13.640000000000002, 87.12, 7.744]

يدعم Vec طرق تكرار متعددة: &v للتكرار للقراءة فقط، و&mut v لتعديل العناصر. أما الطرق ذات الأسلوب الوظيفي .iter() و.map() و.filter() و.sum()، فهي تجعل معالجة البيانات موجزة وأنيقة. تقوم .collect() بتحويل المكرر مرة أخرى إلى Vec.


(4) ▶ المثال:التحويل بين Vec والمصفوفات (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// Conversion between Vec and arrays
// ============================================

fn main() {
    // Array -> Vec: via .to_vec()
    let arr: [i32; 5] = [10, 20, 30, 40, 50];
    let vec_from_arr: Vec<i32> = arr.to_vec();
    println!("Array to Vec: {:?}", vec_from_arr);

    // Array -> Vec: via .iter().copied().collect()
    let vec_via_iter: Vec<i32> = arr.iter().copied().collect();
    println!("Via iter: {:?}", vec_via_iter);

    // Vec -> Array: via try_into() (returns Result)
    let vec_data = vec![1, 2, 3, 4];
    // let bad_array: [i32; 5] = vec_data.try_into().unwrap();  // PANIC: length mismatch
    let ok_array: [i32; 4] = vec_data.try_into().unwrap();
    println!("Vec to array: {:?}", ok_array);

    // Vec -> slice (zero-cost, no copy)
    let slice: &[i32] = &ok_array[1..3];
    println!("Slice of array: {:?}", slice);

    // Vec from iterator
    let squares: Vec<i32> = (1..=5).map(|x| x * x).collect();
    println!("Squares: {:?}", squares);

    // Vec from repeated value
    let repeated = vec!["hello"; 3];
    println!("Repeated: {:?}", repeated);
}

الناتج:

TEXT 📖 للعرض فقط
Array to Vec: [10, 20, 30, 40, 50]
Via iter: [10, 20, 30, 40, 50]
Vec to array: [1, 2, 3, 4]
Slice of array: [20, 30]
Squares: [1, 4, 9, 16, 25]
Repeated: ["hello", "hello", "hello"]

استخدم .to_vec() لتحويل مصفوفة إلى Vec. استخدم .try_into().unwrap() لتحويل Vec إلى مصفوفة — يجب أن تتطابق الأطوال، وإلا سيحدث خطأ فادح. يمكن تحويل Vec إلى شريحة دون أي تكلفة باستخدام &[T]. ويُعد إنشاء Vec من مُكرر باستخدام .collect() تقنية شائعة.


(5) ▶ المثال:تمرين شامل — عربة التسوق وحساب السعر (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example: Vec Insert, Delete, Update, Query + Functional Iteration
// ============================================

#[derive(Debug, Clone)]
struct Item {
    name: String,
    price: f64,
    quantity: u32,
}

impl Item {
    fn new(name: &str, price: f64, quantity: u32) -> Self {
        Item { name: name.to_string(), price, quantity }
    }
    fn subtotal(&self) -> f64 {
        self.price * self.quantity as f64
    }
}

fn print_cart(cart: &[Item]) {
    println!("{:<15} {:>8} {:>6} {:>10}", "Products", "Unit Price", "Quantity", "Subtotal");
    println!("{}", "-".repeat(42));
    for item in cart {
        println!("{:<15} {:>8.2} {:>6} {:>10.2}",
            item.name, item.price, item.quantity, item.subtotal());
    }
    let total: f64 = cart.iter().map(|i| i.subtotal()).sum();
    println!("{}", "-".repeat(42));
    println!("{:<15} {:>8} {:>6} {:>10.2}", "Total", "", "", total);
}

fn main() {
    let mut cart: Vec<Item> = Vec::new();
    cart.push(Item::new("Milk", 5.5, 2));
    cart.push(Item::new("Bread", 8.0, 1));
    cart.push(Item::new("Eggs", 12.5, 3));
    cart.push(Item::new("Cola", 3.0, 4));

    println!("=== Shopping Cart ===");
    print_cart(&cart);

    cart.retain(|i| i.name != "Cola");
    println!("\n--- Remove Cola after ---");
    print_cart(&cart);

    cart.push(Item::new("Butter", 15.0, 2));
    if let Some(eggs) = cart.iter_mut().find(|i| i.name == "Eggs") {
        eggs.quantity += 2;
    }
    println!("\n--- Add Butter + Add Eggs Quantity ---");
    print_cart(&cart);

    let total: f64 = cart.iter().map(|i| i.subtotal()).sum();
    let tax = total * 0.08;
    println!("\nBefore taxes: {:.2}, Tax(8%): {:.2}, Total due: {:.2}", total, tax, total + tax);

    let expensive: Vec<&Item> = cart.iter().filter(|i| i.price > 10.0).collect();
    println!("Unit Price > 10 Items priced at yuan: {:?}", expensive.iter().map(|i| &i.name).collect::<Vec<_>>());
}

الناتج:

TEXT 📖 للعرض فقط
=== Shopping Cart ===
Products               Unit Price   Quantity       Subtotal
------------------------------------------
Milk              5.50      2      11.00
Bread             8.00      1       8.00
Eggs             12.50      3      37.50
Cola              3.00      4      12.00
------------------------------------------
Total                                68.50

--- Remove Cola after ---
Products               Unit Price   Quantity       Subtotal
------------------------------------------
Milk              5.50      2      11.00
Bread             8.00      1       8.00
Eggs             12.50      3      37.50
------------------------------------------
Total                                56.50

--- Add Butter + Add Eggs Quantity ---
Products               Unit Price   Quantity       Subtotal
------------------------------------------
Milk              5.50      2      11.00
Bread             8.00      1       8.00
Eggs             12.50      5      62.50
Butter           15.00      2      30.00
------------------------------------------
Total                               103.50

Before taxes: 103.50, Tax(8%): 8.28, Total due: 111.78
Unit Price > 10 Items priced at yuan: ["Eggs", "Butter"]

يجمع هذا المثال بين استخدام العمليات الأساسية لـ Vec مثل push وretain (الحذف المشروط) وiter_mut().find() (التعديل المشروط) وmap/filter/sum (الإحصاءات الوظيفية). تعد retain أكثر ملاءمة للحذف الجماعي مقارنة بـ remove.



❓ أسئلة شائعة

س: ماذا يحدث عندما يتم تغيير حجم Vec؟ هل الأداء جيد؟ ج: عندما يمتلئ Vec، فإنه يخصص كتلة جديدة من الذاكرة بحجم ضعف الحجم الحالي، وينسخ البيانات القديمة إليها، ثم يحرر الذاكرة القديمة. بعد حساب المتوسط، تظل كل عملية push في درجة O(1) — لأن معظم عمليات push لا تتطلب تغيير الحجم. إذا كنت تعرف الحجم مسبقًا، فاستخدم Vec::with_capacity(n) لتجنب عمليات تغيير الحجم المتعددة.

س: كيف يمكنني الاختيار بين Vec والمصفوفة؟ ج: إذا كان العدد الدقيق للعناصر معروفًا في وقت التحويل البرمجي وكان هذا العدد صغيرًا، فاستخدم مصفوفة؛ أما إذا كان العدد يتغير ديناميكيًا أو غير معروف، فاستخدم Vec. تعمل المصفوفات بشكل أسرع على المكدس (stack)، في حين أن Vec أكثر مرونة على الركام (heap). بشكل عام، يُفضل استخدام Vec ما لم تكن هناك متطلبات محددة تتعلق بالأداء أو تخطيط الذاكرة.

س: ما الفرق بين vec!["a", "b"] وvec!["a"; 2]؟ ج: الأول عبارة عن قائمة من العناصر (يُحسب كل عنصر على حدة)، بينما الثاني عبارة عن نسخة مكررة من القيمة نفسها. vec![0; 5] يُنشئ 5 أصفار (يتطلب أن يكون T: Clone). إذا لم تكن T هي Clone، فلا يمكن استخدام سوى طريقة القائمة.

س: ما الفرق بين .iter() و into_iter()؟ ج: تُرجع .iter() مرجعًا (دون نقل الملكية)، بينما تستهلك into_iter() الكائن Vec وتُرجع مُكررًا يحتفظ بالملكية. .iter() يظل الكائن Vec متاحًا بعد التكرار؛ into_iter() لا يمكن استخدام الكائن Vec الأصلي بعد التكرار.

س: بما أن بيانات Vec تُخزَّن في الـ«هياب»، فما الذي يحتوي عليه متغير Vec الموجود في الـ«ستاك»؟ ج: يحتوي الـ«ستاك» على ثلاث قيم usize: مؤشر إلى بيانات الـ«هياب»، والطول (len)، والسعة (capacity)، بإجمالي 24 بايت (في نظام 64 بت). توجد البيانات الفعلية في الـ heap. ولذلك، فإن متغير Vec نفسه صغير جدًّا؛ ولا يتطلب نسخه سوى نسخ هذه الـ 24 بايت من «المؤشرات الضخمة».


📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: أنشئ متجهًا Vec<i32> يحتوي على الأرقام من 1 إلى 10، واستخدم push لإضافة 11 و12، ثم استخدم pop لإزالة العناصر الثلاثة الأخيرة، وأخيرًا اطبع المتجه Vec.
  2. الصعوبة ⭐⭐: اكتب دالة fn remove_evens(v: &mut Vec<i32>) تزيل جميع الأعداد الزوجية من Vec وتحتفظ بالأعداد الفردية. اختبرها في main باستخدام vec![1, 2, 3, 4, 5, 6, 7, 8]؛ وينبغي أن تكون النتيجة [1, 3, 5, 7].
  3. الصعوبة ⭐⭐⭐: قم بمحاكاة برنامج عربة التسوق. عرّف struct Item { name: String, price: f64, quantity: u32 }. أنشئ عربة تسوق Vec<Item> وقم بتنفيذ ما يلي: إضافة عناصر، وحذف عناصر حسب الاسم، وتعديل الكميات، وطباعة السعر الإجمالي لعربة التسوق. اشرح العملية الكاملة في main.
Web-Tutorial.com

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

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

100%