هل تعرف ذلك الشعور عندما تبدأ في تعلم لغة جديدة وتتوقع أن تنطلق بسرعة البرق، ولكنك تتعثر في الخطوة الأولى بدلاً من ذلك؟ هذا ما حدث معي مع Rust. كان لدي أداة مساعدة بلغة JavaScript تعمل بشكل ممتاز — شيء يقوم بتحليل وتعديل بعض المصفوفات — واعتقدت أنني سأعيد كتابتها خلال استراحة قهوة. فتحت المحرر، وكتبت ما بدا لي كأنه كود طبيعي، ثم شغلت المترجم. فانفجر! ليس بسبب أخطاء في بناء الجملة (syntax errors)، ولا بسبب فقدان الفواصل المنقوطة. بل أخبرني أنني حاولت استخدام قيمة تم نقل ملكيتها بالفعل. حدقت في الشاشة. نُقلت؟ لم أنقل أي شيء. لقد قمت فقط بتعيينها لمتغير آخر.

منطقة الراحة في JavaScript

تدربك JavaScript على التعامل مع البيانات مثل سبورة مشتركة. تقوم بتعريف كائن أو مصفوفة، ثم تمررها إلى دالة، وتترك تلك الدالة تضيف خاصية، ثم تمررها إلى مكان آخر. يعمل جامع المهملات (garbage collector) في الخلفية مثل عامل نظافة غير مرئي، ينتظر لجمع كل ما تتركه خلفك. أنت لا تسأل أبدًا: "من يملك هذا النص؟" بل تسأل: "هل يمكنني قراءته؟" إذا كانت الإجابة نعم، فإنك تلمسه. إذا كنت بحاجة إلى مرجع آخر، فما عليك سوى قول let b = a والاستمرار. يشير كلا المتغيرين إلى نفس الجزء من الذاكرة، وإذا قام a بتعديله، سيرى b التغيير فورًا. إنه أمر مريح، ولكنه فوضوي أيضًا، لكن هذه الفوضى نادرًا ما تؤذيك لأن وقت التشغيل يتولى عملية التنظيف.

صدمة المترجم الأولى

Rust لا تثق بك. قد يبدو هذا كلامًا قاسيًا، لكنه أول شيء ستتعلمه. بُنيت اللغة حول نظام ملكية بقواعد صارمة ثلاث. أولاً، لكل قيمة مالك واحد فقط. ثانيًا، يوجد مالك واحد فقط في كل مرة. ثالثًا، عندما يخرج ذلك المالك عن النطاق، تقوم Rust بالتخلص من القيمة تلقائيًا. لا يوجد جامع مهملات، ولا يوجد عداد مراجع في الخلفية ما لم تختر ذلك صراحةً. فقط هذه القواعد الثلاث، التي يفرضها المترجم قبل تشغيل الكود الخاص بك.

عندما كتبت نسختي من تلك الأداة بلغة Rust، فعلت ما بدا لي طبيعيًا. أنشأت متجهًا (vector)، وهو رد Rust على المصفوفة، وخصصته لمتغير ثانٍ. ثم حاولت استخدام المتغير الأول. رفض المترجم ذلك. في JavaScript، يقوم let b = a بنسخ المرجع. أما في Rust، فإنه ينقل الملكية. يصبح المتغير الأصلي غير صالح. يفعل المترجم ذلك لضمان عدم وجود مسارين يتصادمان بالخطأ مع نفس الذاكرة، وهو ما يؤدي إلى حدوث سباقات البيانات وأخطاء "الاستخدام بعد التحرير" في الأنظمة الأخرى.

لماذا تسلبك Rust ألعابك

انظر إلى هذا النمط في JavaScript:

let a = [1, 2, 3];
let b = a;
a.push(4);
// Both a and b see 4.

إنه أمر بديهي. لن تفكر مرتين. في Rust، ستجعلك نفس هذه البديهة تُرفض من قبل المترجم قبل أن تنهي الكتابة. بمجرد أن يمتلك b المتجه، يصبح a مجرد اسم فارغ. لا يمكنك الإضافة إليه. لا يمكنك حتى النظر إليه. الذاكرة أصبحت ملكًا لـ b الآن.

يبدو هذا وكأنه عقاب حتى تدرك ما يمنعه. إذا كان بإمكان متغيرين تعديل نفس بيانات الذاكرة المكدسة دون تنسيق، فستخاطر بفساد الذاكرة. تقضي Rust على هذه الفئة الكاملة من الأخطاء من خلال جعل عملية النقل صريحة. المترجم ليس متشددًا، بل يعمل كحارس بوابة. إنه يجبرك على تحديد، في كل خطوة، أي جزء من الكود الخاص بك مسؤول عن أي بيانات.

الاستعارة: الحل البديل الذي هو في الواقع ميزة

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

تتيح لك الاستعارة استخدام قيمة دون أخذها. هناك نوعان، والتمييز بينهما مهم.

  • الاستعارات غير القابلة للتعديل، وتُكتب &T: تتيح لك قراءة البيانات. يمكنك الحصول على أي عدد تريده في نفس