أهم أخطاء مطوري Rust المبتدئين: المستوى المتقدم
Rust هي لغة تعد بالعمل الآمن مع الذاكرة والأداء العالي. ومع ذلك، عندما تنتقل من الأمثلة الأساسية إلى المشاريع الحقيقية، تبدأ في الظهور أخطاء لا يمكن حلها بمجرد إضافة & أو mut. في هذه المقالة، سنستعرض أربعة فخاخ متقدمة يقع فيها غالبًا المطورون الذين أتقنوا بالفعل أساسيات borrow checker والملكية.
المقالة موجهة لأولئك الذين كتبوا بالفعل عدة مشاريع بلغة Rust وتعاملوا مع Rc و RefCell و الـ traits و unsafe. لن نشرح ما هو الاقتراض، بل سنركز على المزالق الخفية.
1. تسرب الذاكرة عبر المراجع الدائرية في Rc/RefCell
الخطأ الأكثر غدرًا هو الاعتقاد بأن Rc يمنع تسرب الذاكرة تلقائيًا. في الواقع، يستخدم Rc عدّ المراجع، وإذا كان هناك كائنان يشير كل منهما إلى الآخر، فلن يصبح العداد صفرًا أبدًا. لن يتم تحرير الذاكرة.
لننظر إلى مثال كلاسيكي مع رسم بياني:
use std::rc::Rc;use std::cell::RefCell;
#[derive(Debug)]struct Node { value: i32, children: Vec<Rc<RefCell<Node>>>,}
fn main() { let a = Rc::new(RefCell::new(Node { value: 1, children: vec![] })); let b = Rc::new(RefCell::new(Node { value: 2, children: vec![] }));
// إنشاء مرجع دائري a.borrow_mut().children.push(Rc::clone(&b)); b.borrow_mut().children.push(Rc::clone(&a));
// بعد الخروج من main، لن يتم تحرير الذاكرة! drop(a); drop(b); // تسرب: Rc يعتقد أن هناك مراجع أخرى}لماذا يحدث هذا؟ بعد drop(a)، يقل عداد مراجع a، لكن b لا يزال يحتوي على مرجع لـ a عبر children. وبالمثل بالنسبة لـ b. في النهاية، يظل كلا الكائنين في الذاكرة.
الحل: استخدم Weak<T> للمراجع الضعيفة. في الرسوم البيانية، عادةً ما تكون إحدى العقد هي المالك (مرجع قوي)، والباقي تكون مراجع ضعيفة.
use std::rc::{Rc, Weak};use std::cell::RefCell;
#[derive(Debug)]struct Node { value: i32, parent: RefCell<Weak<Node>>, // مرجع ضعيف للوالد children: RefCell<Vec<Rc<Node>>>, // مراجع قوية للأطفال}
fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), });
let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), });
*leaf.parent.borrow_mut() = Rc::downgrade(&branch); // الآن عند حذف branch، سيتم تحرير leaf أيضًا}2. الاستخدام الخاطئ لـ Pin مع الهياكل ذاتية المرجع
الهياكل التي تحتوي على مراجع لنفسها (self-referential) هي سبب شائع للسلوك غير المحدد. لا تسمح Rust بإنشائها مباشرة، لكن عبر Pin و unsafe يصبح ذلك ممكنًا. الخطأ هو نسيان أن Pin يضمن فقط أن الكائن لن يتم نقله في الذاكرة، لكنه لا يحمي من الإزاحات الداخلية.
مثال مع المستقبل (future):
use std::pin::Pin;use std::marker::PhantomPinned;
#[derive(Debug)]struct SelfReferential { data: String, pointer: *const String, // مؤشر خام على data _pin: PhantomPinned,}
impl SelfReferential { fn new(data: String) -> Self { SelfReferential { pointer: std::ptr::null(), data, _pin: PhantomPinned, } }
fn init(self: Pin<&mut Self>) { let this = unsafe { self.get_unchecked_mut() }; this.pointer = &this.data as *const String; }}