تخزين لائحة من القيم باستخدام الأشعة (Vectors) في لغة رست Rust
تُعد لغة رست Rust واحدة من اللغات الحديثة التي اكتسبت شعبية واسعة بفضل ميزاتها القوية في إدارة الذاكرة والأداء العالي مع الحفاظ على أمان الذاكرة. من بين الأدوات الأساسية التي توفرها لغة رست للتعامل مع مجموعات البيانات الديناميكية هي الأشعة (Vectors)، والتي تمثل بنية بيانات مرنة تسمح بتخزين لائحة من القيم مع إمكانية التوسع أو التصغير أثناء وقت التشغيل.
في هذا المقال سنغوص عميقاً في مفهوم الأشعة (Vectors) في رست، وسنشرح كيفية تخزين لائحة من القيم باستخدامها، بالإضافة إلى توضيح المزايا التي تقدمها، والطرق المتنوعة للتعامل معها، وكيفية الاستفادة من ميزات لغة رست في هذا السياق. سنناقش كذلك الجانب الداخلي لبنية الـ Vector من حيث الذاكرة وإدارة الموارد، مما يثري فهم القارئ لهذه البنية الحيوية.
مقدمة عن الأشعة (Vectors) في رست
في لغات البرمجة التقليدية، يُستخدم المصفوفات Arrays لتخزين مجموعة من القيم ذات النوع الواحد. إلا أن المصفوفات في رست مثل معظم اللغات تكون ذات حجم ثابت يعرف أثناء وقت الترجمة (compile time)، مما يعني عدم إمكانية تغيير حجمها بعد إنشائها. في المقابل، الأشعة (Vectors) في رست تمثل بنية بيانات ديناميكية، حيث يمكن إضافة أو إزالة العناصر أثناء وقت التشغيل (runtime)، مما يجعلها مثالية لتخزين القوائم التي قد تتغير أحجامها.
الأشعة (Vectors) في رست عبارة عن حاويات ديناميكية تُخزن مجموعة متجانسة من القيم من نفس النوع، وتوفر إمكانية الوصول العشوائي للعناصر، كما تدعم العديد من العمليات مثل الإضافة، الحذف، التكرار، والفرز.
كيفية إنشاء وتخزين لائحة من القيم باستخدام Vector
إنشاء Vector فارغ
لبداية تخزين لائحة من القيم باستخدام Vector، يمكن إنشاء Vector فارغ من نوع معين كالتالي:
rustlet mut numbers: Vec<i32> = Vec::new();
في هذا المثال، numbers هو متجه من الأعداد الصحيحة (i32)، والـ mut تعني أنه متغير قابل للتغيير (mutable)، أي يمكن تعديل محتوى المتجه لاحقاً.
إضافة عناصر إلى Vector
بعد إنشاء المتجه، يمكن إضافة العناصر إليه باستخدام الدالة push:
rustnumbers.push(10);
numbers.push(20);
numbers.push(30);
بهذا الشكل أصبح المتجه يحتوي على ثلاث قيم: 10، 20، و30.
إنشاء Vector مباشرة مع قيم مبدئية
يمكن أيضاً إنشاء Vector وتعبئته بالقيم مباشرة عند الإنشاء باستخدام الماكرو vec!:
rustlet mut numbers = vec![10, 20, 30];
يتم هنا إنشاء متجه يحتوي على القيم الثلاثة المذكورة، ويمكن تعديل هذا المتجه لاحقاً إذا كان معرفاً كـ mut.
الوصول إلى عناصر Vector
الوصول باستخدام الفهرس Indexing
يمكن الوصول إلى أي عنصر في الـ Vector عبر الفهرس:
rustlet first = numbers[0]; // القيمة 10
let second = numbers[1]; // القيمة 20
ولكن يجب الحذر من أن الفهرس يجب أن يكون ضمن حدود الـ Vector، لأن محاولة الوصول إلى فهرس خارج الحدود يؤدي إلى حدوث خطأ وقت التشغيل.
استخدام الدالة get
للتعامل مع إمكانية عدم وجود عنصر عند فهرس معين بطريقة أكثر أماناً، يمكن استخدام الدالة get التي تعيد Option:
rustmatch numbers.get(2) {
Some(value) => println!("القيمة عند الفهرس 2 هي {}", value),
None => println!("لا يوجد قيمة عند هذا الفهرس"),
}
التكرار على عناصر Vector
يمكن التكرار على محتوى الـ Vector بعدة طرق:
باستخدام حلقة for التقليدية
rustfor num in &numbers {
println!("{}", num);
}
هنا &numbers تعني أننا نأخذ مرجعاً غير قابل للتغيير على المتجه، مما يسمح بالقراءة فقط دون تعديل.
التكرار مع تعديل القيم
إذا كان المتجه معرفاً كـ mut، يمكن التكرار على مراجع قابلة للتغيير وتعديل القيم:
rustfor num in &mut numbers {
*num += 5; // زيادة كل عنصر بمقدار 5
}
حذف العناصر من Vector
يمكن حذف العناصر من Vector بعدة طرق:
حذف العنصر الأخير باستخدام pop
rustlet last = numbers.pop();
match last {
Some(value) => println!("تم حذف القيمة: {}", value),
None => println!("المتجه فارغ"),
}
pop تعيد آخر عنصر في المتجه وتزيله منه.
حذف عنصر في موقع معين
باستخدام remove يمكن حذف عنصر عند فهرس معين:
rustnumbers.remove(1); // يحذف العنصر عند الفهرس 1 (العنصر الثاني)
تغيير حجم Vector
ميزة الـ Vector الأساسية هي القدرة على تغيير الحجم ديناميكياً. يمكن إضافة عناصر جديدة باستخدام push أو حذفها باستخدام pop أو remove. هذا يتيح المرونة في تخزين لوائح قيم متغيرة الحجم دون الحاجة إلى إنشاء مصفوفات جديدة.
كيفية تخزين أنواع مختلفة من القيم
عادةً، الـ Vector يخزن عناصر من نفس النوع. ومع ذلك، يمكن تخزين أنواع مختلفة عبر استخدام النوع enum أو نوع صناديق ذكية مثل Box، مما يوسع إمكانيات التخزين.
مثال باستخدام enum
rustenum Value {
Integer(i32),
Float(f64),
Text(String),
}
let mut values: Vec = Vec::new();
values.push(Value::Integer(42));
values.push(Value::Float(3.14));
values.push(Value::Text(String::from("مرحبا")));
الأداء الداخلي للـ Vector
من حيث الأداء، يستخدم الـ Vector مصفوفة مخصصة في الذاكرة، وعندما يمتلئ الـ Vector، يقوم بتخصيص مساحة ذاكرة أكبر نسبيًا (عادة ضعف الحجم السابق) ويقوم بنسخ العناصر إلى الموقع الجديد، ثم يحرر الموقع القديم. هذه التقنية تضمن أن عملية الإضافة push تكون فعالة بشكل متوسط (amortized constant time).
الذاكرة وإدارة الموارد في Vector
لغة رست تشتهر بنموذج الملكية Ownership، وهذا ينعكس في كيفية إدارة Vector للذاكرة. عندما يتم تدمير Vector، تتحرر الذاكرة المخصصة له تلقائياً. كذلك، عند نقل Vector إلى متغير آخر (move)، تنتقل الملكية دون نسخ البيانات، مما يحسن الأداء.
أمثلة عملية متقدمة على استخدام Vector
دمج عدة متجهات في متجه واحد
rustlet mut v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
v1.extend(v2);
println!("{:?}", v1); // [1, 2, 3, 4, 5, 6]
تصفية قيم معينة من Vector
rustlet numbers = vec![1, 2, 3, 4, 5, 6];
let even_numbers: Vec<i32> = numbers.into_iter().filter(|x| x % 2 == 0).collect();
println!("{:?}", even_numbers); // [2, 4, 6]
تحويل Vector إلى مجموعة من البيانات المهيكلة Structs
ruststruct Person {
name: String,
age: u32,
}
let names = vec!["أحمد", "سارة", "يوسف"];
let ages = vec![30, 25, 40];
let people: Vec = names. into_iter().zip(ages.into_iter())
.map(|(name, age)| Person { name: name.to_string(), age })
.collect();
جدول مقارنة بين Array و Vector في رست
| الخاصية | Array | Vector |
|---|---|---|
| الحجم | ثابت (معروف أثناء الترجمة) | ديناميكي (يتغير أثناء التشغيل) |
| قابلية التوسع | لا | نعم |
| الأداء عند الوصول | سريع جدًا | سريع جدًا |
| الاستخدام | عند الحاجة إلى حجم ثابت | عند الحاجة إلى حجم متغير |
| قابلية التعديل | نعم (إذا كان mut) |
نعم (إذا كان mut) |
| إمكانية التخزين المختلط | لا (يجب أن تكون نفس النوع) | ممكن باستخدام enum أو Traits |
الخلاصة
الأشعة (Vectors) في لغة رست تشكل إحدى الأدوات الجوهرية والفعالة لتخزين لوائح من القيم بشكل ديناميكي ومرن. بفضل إدارة الذاكرة الذكية التي توفرها رست، يمكن للمبرمجين العمل بثقة على لوائح بيانات متغيرة الحجم دون القلق من مشاكل تسرب الذاكرة أو التداخلات غير الآمنة. كما أن التوافق الكبير مع ميزات لغة رست الأخرى مثل نموذج الملكية Ownership والتعامل مع الأنواع الديناميكية يجعل الـ Vector حلاً مثالياً لمختلف التطبيقات التي تتطلب تخزين بيانات متغيرة الحجم.
استخدام الـ Vector في رست ليس فقط لتخزين القيم، بل يشمل قدرات واسعة من التكرار، التعديل، التصفية، والدمج مما يجعله من الأدوات التي لا غنى عنها في بناء البرمجيات الحديثة.
المصادر والمراجع
-
The Rust Programming Language, Steve Klabnik and Carol Nichols, 2019.
-
وثائق لغة رست الرسمية: https://doc.rust-lang.org/std/vec/struct.Vector.html

