Rust: متجهات Rust (Vec)
آخر تحديث: 2026-08-26
Vec<T>(vector) هو المصفوفة الديناميكية الأكثر استخدامًا في المكتبة القياسية لـ Rust — حيث يمكن توسيعها أو تقليصها أثناء وقت التشغيل، ويتم تخصيص جميع البيانات في الـ heap. وهي تعادل في Rust ما يُعرف بـ «ArrayList» أو «قائمة Python».
إذا كانت المصفوفات أشبه بـ«خزائن تخزين ذات حجم ثابت»، فإن Vec يشبه «مستودعًا قابلاً للتوسيع» — فهو يتوسع تلقائيًا عند نفاد المساحة، وبالتالي لا تضطر إلى إدارة الذاكرة يدويًّا.
1. ما ستتعلمه
- إنشاء مصفوفات ديناميكية باستخدام الماكرو
vec!وVec::new() - استخدم عمليتي «push» و«pop» لإضافة عناصر إلى نهاية القائمة أو إزالتها منها
- استخدم
insertوremoveلإضافة عناصر في المنتصف أو إزالتها - فهم الفرق بين السعة والطول
- تصفح المصفوفات المتجهة (Vecs) والتكرار عليها، والتحويل بين المصفوفات المتجهة والمصفوفات العادية
2. قصة أمينة صندوق في سوبرماركت
(1) الإحباط: عدم معرفة حجم سلة التسوق
يعمل توم على تطوير نظام للدفع عند الخروج في السوبرماركت. عربة التسوق الخاصة بالعميل:
// 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
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) نظرة عامة على المفهوم
graph LR
A[Vec<T> 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 (مستوى الصعوبة ⭐)
// ============================================
// 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());
}
الناتج:
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.pushتضيف عنصرًا إلى نهاية القائمة، وpopتزيل عنصرًا من نهاية القائمة (وتُرجعOption<T>).len()تُرجع عدد العناصر الموجودة في القائمة.
(2) ▶ المثال:الإدراج/الإزالة وإدارة السعة (مستوى الصعوبة ⭐⭐)
// ============================================
// 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());
}
الناتج:
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) ▶ المثال:طرق التجول والتكرار في المصفوفات (مستوى الصعوبة ⭐⭐)
// ============================================
// 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);
}
الناتج:
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 والمصفوفات (مستوى الصعوبة ⭐⭐)
// ============================================
// 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);
}
الناتج:
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) ▶ المثال:تمرين شامل — عربة التسوق وحساب السعر (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// 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<_>>());
}
الناتج:
=== 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 بايت من «المؤشرات الضخمة».
📖 ملخص
Vec<T>هو مصفوفة مخصصة ديناميكيًا في الـ«هياب» يمكن تغيير حجمها أثناء وقت التشغيل- طريقة الإنشاء:
Vec::new()،vec![]،Vec::with_capacity(n) - عمليات الذيل:
pushإضافة (O(1) مع توزيع الحمل)،popإزالة (O(1)) - عملية متوسطة:
insert/remove(O(n)، يجب إزاحة البيانات) - السعة مقابل الطول:
capacityهي المساحة المخصصة؛lenهو العدد الفعلي للعناصر؛shrink_to_fitيحرر الذاكرة الزائدة - يمكن تحويل المصفوفات المتجهة (Vecs) والمصفوفات العادية (arrays) في كلا الاتجاهين، كما يمكن تحويل المصفوفات المتجهة إلى شرائح دون أي تكلفة
&[T]
📝 تمارين
- الصعوبة ⭐: أنشئ متجهًا
Vec<i32>يحتوي على الأرقام من 1 إلى 10، واستخدمpushلإضافة 11 و12، ثم استخدمpopلإزالة العناصر الثلاثة الأخيرة، وأخيرًا اطبع المتجه Vec. - الصعوبة ⭐⭐: اكتب دالة
fn remove_evens(v: &mut Vec<i32>)تزيل جميع الأعداد الزوجية من Vec وتحتفظ بالأعداد الفردية. اختبرها فيmainباستخدامvec![1, 2, 3, 4, 5, 6, 7, 8]؛ وينبغي أن تكون النتيجة[1, 3, 5, 7]. - الصعوبة ⭐⭐⭐: قم بمحاكاة برنامج عربة التسوق. عرّف
struct Item { name: String, price: f64, quantity: u32 }. أنشئ عربة تسوقVec<Item>وقم بتنفيذ ما يلي: إضافة عناصر، وحذف عناصر حسب الاسم، وتعديل الكميات، وطباعة السعر الإجمالي لعربة التسوق. اشرح العملية الكاملة فيmain.