المؤشر Rc الذكي واستخدامه للإشارة إلى عدد المراجع في لغة رست Rust
تعتبر لغة رست (Rust) من اللغات البرمجية الحديثة التي نالت شهرة واسعة بين مطوري البرمجيات نظرًا لتركيزها القوي على السلامة في إدارة الذاكرة، والأداء العالي، مع القدرة على التحكم الكامل بالمؤشرات والموارد. من أهم التحديات التي تواجه مبرمجي لغات النظام هي كيفية إدارة الذاكرة بشكل آمن وفعّال دون التسبب بتسريبات أو أخطاء في الوصول إلى البيانات. لتلبية هذه الحاجة، تقدم رست أدوات متعددة لإدارة الملكية (ownership) والاقتراض (borrowing) بناءً على قواعد صارمة تساعد في ضمان سلامة الذاكرة وقت الترجمة، دون الحاجة لجمع قمامة Garbage Collector تقليدي. من بين هذه الأدوات التي تقدمها رست للمطورين هو المؤشر الذكي Rc
ما هو المؤشر الذكي Rc في رست؟
المؤشر الذكي Rc
في النظام العادي للملكية في رست، هناك قاعدة واضحة تمنع وجود أكثر من مالك واحد mutable (قابل للتغيير) للبيانات في الوقت نفسه، أو وجود عدة مالكين ولكن فقط للبيانات غير القابلة للتغيير (immutable). هذا لضمان سلامة البيانات ومنع حالات السباق في الذاكرة.
لكن في بعض الحالات البرمجية، يحتاج المطور إلى مشاركة ملكية البيانات بين عدة كائنات أو مؤشرات، بحيث لا يتم حذف البيانات إلا عندما لا يبقى أي مؤشر يشير إليها. هنا يأتي دور Rc
خصائص Rc
-
عد المرجع (Reference Counting): يقوم Rc
بتتبع عدد المؤشرات التي تشير إلى نفس البيانات. كلما أنشأت نسخة جديدة من Rc ، يرتفع العد، وعند انتهاء أي مؤشر منها، ينخفض العد. -
تخزين على الـ heap: تقوم Rc
بتخزين البيانات في الذاكرة الديناميكية (heap)، وليس على المكدس (stack). -
مشاركة ملكية البيانات: يسمح Rc
لمجموعة من المؤشرات بمشاركة ملكية نفس البيانات. -
عدم قابلية التغيير (Immutability): لا يسمح Rc
بالتغيير المباشر على البيانات بشكل افتراضي، حيث تكون البيانات التي يتم مشاركتها عبر Rc غير قابلة للتغيير، ما لم يتم استخدام أدوات إضافية مثل RefCell لتوفير إمكانية تعديل بيانات داخل Rc . -
ليس آمنًا عبر الخيوط (Not Thread-Safe): Rc
مصمم للاستخدام داخل خيط تنفيذ واحد (single-threaded)، وإذا كانت هناك حاجة لمشاركة البيانات بين عدة خيوط، يتم استخدام Arc (Atomic Reference Counted).
آلية عمل Rc وعداد المراجع
في كل مرة يتم إنشاء مؤشر Rcclone() على Rc
هذه الطريقة تتيح إدارة ذاكرة آمنة وفعالة بدون تدخل المستخدم في عمليات الحذف، ما يقلل من احتمالية حدوث أخطاء مثل “استخدام بعد الحذف” أو “تسريبات الذاكرة”.
كيف يُحتفظ بعداد المراجع داخليًا؟
يحتوي Rc
مثال توضيحي
rustuse std::rc::Rc;
fn main() {
let rc_a = Rc::new(String::from("مرحبًا بالعالم"));
println!("عدد المراجع بعد الإنشاء: {}", Rc::strong_count(&rc_a)); // 1
let rc_b = Rc::clone(&rc_a);
println!("عدد المراجع بعد النسخ: {}", Rc::strong_count(&rc_a)); // 2
{
let rc_c = Rc::clone(&rc_a);
println!("عدد المراجع داخل النطاق: {}", Rc::strong_count(&rc_a)); // 3
}
println!("عدد المراجع بعد انتهاء النطاق: {}", Rc::strong_count(&rc_a)); // 2
}
في المثال أعلاه، نلاحظ كيف أن عداد المراجع يزداد وينقص مع نسخ المؤشرات وانتهاء نطاقها.
حالات الاستخدام المناسبة لـ Rc
-
مشاركة البيانات غير القابلة للتغيير: عندما يحتاج أكثر من جزء في البرنامج إلى قراءة نفس البيانات بدون تعديلها.
-
الهياكل البيانية (Data Structures) المعقدة: مثل الأشجار أو الرسوم البيانية حيث تحتاج العقد إلى امتلاك مراجع متعددة لعناصر أخرى.
-
مشاركة الحالة بين عدة مكونات: في البرامج التي تتطلب مشاركة معلومات أو حالات بين أجزاء مختلفة من التطبيق دون الحاجة إلى نسخ البيانات نفسها.
مثال على هياكل البيانات المعقدة
يمكن استخدام Rc
لهذا الغرض، تقدم رست نوعًا ذكيًا آخر يدعى Weak يمكن استخدامه لكسر الحلقة المرجعية بين Rc
الفرق بين Rc و Arc
رغم تشابه Rc
-
Rc
مخصص للاستخدام داخل خيط تنفيذ واحد، حيث لا توجد مشاكل سباق في تعديل العداد المرجعي. -
Arc
هو نسخة مؤمنة عبر الخيوط (thread-safe) باستخدام آليات التزامن الذري atomic synchronization، مما يسمح بمشاركة البيانات بين خيوط متعددة في نفس الوقت.
لذلك، في بيئات متعددة الخيوط (multithreading) يجب استخدام Arc
Rc مع RefCell لتعديل البيانات المشتركة
نظرًا لأن Rc
التركيب التالي شائع في رست:
rustuse std::rc::Rc;
use std::cell::RefCell;
fn main() {
let data = Rc::new(RefCell::new(5));
{
let mut value = data.borrow_mut();
*value += 1;
}
println!("القيمة بعد التعديل: {}", data.borrow());
}
في هذا المثال، RefCell
قيود ومخاطر استخدام Rc
رغم فوائد Rc
-
الحلقات المرجعية (Reference Cycles): إذا قمت بإنشاء حلقة من مؤشرات Rc
التي تشير إلى بعضها البعض، فإن عداد المراجع لن يصل إلى صفر أبدًا، مما يؤدي إلى تسريب الذاكرة. لذلك، من المهم استخدام Weak لكسر هذه الحلقات. -
عدم الأمان عبر الخيوط: لا يمكن استخدام Rc
بأمان في البرامج متعددة الخيوط. -
تكلفة حسابية: عداد المراجع في Rc
يضيف تكلفة زمنية وذاكرة إضافية مقارنة بالمؤشرات العادية.
الأدوات المرتبطة بـ Rc في مكتبة رست القياسية
-
Rc::clone(&rc): لزيادة عداد المراجع وإنشاء نسخة جديدة من Rc
. -
Rc::strong_count(&rc): لاسترجاع عدد المراجع القوية الحالية إلى البيانات.
-
Rc::weak_count(&rc): لاسترجاع عدد المراجع الضعيفة (Weak references) التي تشير إلى البيانات.
-
Rc::downgrade(&rc): لتحويل Rc
إلى Weak .
جدول مقارنة بين Rc و Weak
| الخاصية | Rc |
Weak |
|---|---|---|
| نوع المؤشر | قوي (Strong reference) | ضعيف (Weak reference) |
| تأثير على عداد المراجع | يزيد العداد ويمنع تحرير البيانات | لا يزيد العداد، لا يمنع تحرير البيانات |
| قابلية الوصول | دائمًا يمكن الوصول إلى البيانات | يمكن أن تصبح البيانات غير متاحة إذا انتهى Rc |
| الاستخدام | المشاركة في ملكية البيانات | كسر الحلقات المرجعية لتجنب التسريبات |
| إمكانية التعديل | بدون تعديل مباشر | لا يمكن تعديل البيانات عبر Weak مباشرة |
تأثير Rc على تصميم البرامج في رست
يساعد استخدام Rc
وبفضل دعمها في قواعد اللغة، فإن أي محاولة لاستخدام Rc
تطبيقات عملية لمؤشر Rc
في التطبيقات التي تتطلب مشاركة بيانات غير قابلة للتغيير بين عدة مكونات في نفس الخيط، مثل تطبيقات الواجهات الرسومية أو الألعاب، غالبًا ما يتم استخدام Rc
كذلك، تستخدم Rc
خلاصة
يُعد المؤشر الذكي Rc
بالرغم من عدم كونه آمنًا عبر الخيوط، يظل Rc
لهذا، فإن الفهم العميق لكيفية عمل Rc
المراجع:
-
The Rust Programming Language (The Book), Steve Klabnik and Carol Nichols, 2018.
-
Rust Standard Library Documentation – std::rc::Rc, https://doc.rust-lang.org/std/rc/struct.Rc.html

