اذهب إلى المحتوى

الحزم packages والوحدات المصرفة crates في لغة رست Rust


Naser Dakhel

سنُغطّي أولى أجزاء نظام الوحدة ألا وهو الحزم packages والوحدات المصرّفة crates.

الوحدة مُصرَّفة هي الجزء الأصغر من الشيفرة البرمجية التي يستطيع المصرّف التعرف عليها في المرة الواحدة. حتى إذا شغّلنا rustc بدلًا من cargo ومررنا ملفًا مصدريًا للشيفرة، سيتعامل المصرّف مع هذا الملف على أنه وحدة مُصرَّفة. يمكن أن تحتوي الوحدات المُصرَّفة وحدات modules يمكن أن تكون معرّفة في ملفات أخرى مُصرّفة مع تلك الوحدة المُصرَّفة، كما سنرى في الأقسام التالية.

يُمكن أن تكون الوحدة المُصرَّفة ثنائية binary أو أن وحدة مكتبة مُصرَّفة library، وتُعد الوحدات المُصرَّفة الثنائية برامجًا يُمكنك تصريفها إلى ملف تنفيذي ومن ثم تشغيلها مثل برامج سطر الأوامر command line programs أو الخوادم servers، ويجب أن تحتوي الوحدات المُصرَّفة الثنائية على دالة تدعى main تُعرِّف ما الذي يحدث عند تشغيل الملف التنفيذي، وجميع الوحدات المُصرَّفة التي أنشأناها حتى اللحظة هي وحدات مصرّفة ثنائية.

لا تحتوي الوحدات المكتبية المصرّفة على دالة main ولا تُصرَّف إلى ملف تنفيذي، وإنما تعرِّف وظائف بُنيَت بهدف الاستفادة منها من خلال مشاركتها من قبل عدّة مشاريع، ونذكر منها على سبيل المثال وحدة المكتبة المصرفة rand المُستخدمة في مقال لُعبة تخمين الأرقام السابق، والتي تقدّم خاصية توليد الأرقام العشوائية. عندما يذكر مستخدمو رست مصطلح وحدة مصرّفة، فهم يقصدون وحدة المكتبة المصرّفة، ويعتمدون مفهوم الوحدة المصرّفة على نحو تبادلي لمصطلح "المكتبة" السائد في مفهوم البرمجة العام.

وحدة الجذر المصرّفة crate root هي الملف المصدري الذي يبدأ منه مصرّف رست ويشكّل وحدة الجذر للوحدة المصرّفة crate، وسنشرح الوحدات بتفصيلٍ أكبر في الفقرات اللاحقة.

الحزمة هي مجمع لوحدة مصرّفة واحدة أو أكثر، وتقدّم مجموعةً من الوظائف، وتحتوي على ملف "Cargo.toml" يصِف كيفية بناء الوحدات المصرّفة داخله. كارجو Cargo هي حزمة تحتوي على وحدة ثنائية مُصرّفة مخصصة لأداة سطر الأوامر المُستخدمة لبناء شيفرتك البرمجية، وتحتوي أيضًا على وحدة مكتبة مصرّفة تعتمد عليها الوحدة الثنائية المُصرّفة. يمكن أن تعتمد المشاريع الأخرى على وحدة مكتبة كارجو المُصرّفة لاستخدام نفس المنطق المُستخدم في أداة سطر أوامر كارجو.

هناك عدّة قواعد تُملي ما على الحزمة أن تحتويه، إذ يمكن أن تحتوي الحزمة على وحدة مكتبة مُصرّفة واحدة فقط، ويمكن أن تحتوي على عدد من الوحدات الثنائية المُصرّفة بحسب حاجتك إلا أنها يجب أن تحتوي على الأقل على وحدةٍ مصرّفة واحدةٍ على الأقل بغضّ النظر عن نوعها سواءٌ كانت وحدة ثنائية أو وحدة مكتبة.

دعنا ننظر ما الذي يحدث عندما نُنشئ حزمة. نُدخل أوّلًا الأمر cargo new:

$ cargo new my-project
     Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs

بعد تنفيذ الأمر السابق، نستخدم الأمر ls لنرى ماذا أنشأ كارجو، إذ سنجد ضمن مجلد المشروع ملفًا اسمه Cargo.toml، والذي يمنحنا حزمة، ونجد أيضًا مجلد "src" يحتوي على الملف "main.rs". وإذا نظرنا إلى Cargo.toml فلن نجد هناك أي ذكر للملف src/main.rs، وذلك لأن كارجو يتبع اصطلاحًا معيّنًا بحيث يكون src/main.rs الوحدة الجذر للوحدة الثنائية المصرّفة باستخدام نفس اسم الحزمة. يعلم كارجو أيضًا أنه إذا احتوى مجلد الحزمة على src/lib.rs فهذا يعني أن الحزمة تحتوي على وحدة مكتبة مصرّفة باسم الحزمة ذاته و src/lib.rs هي الوحدة الجذر في هذه الحالة. يُمرّر كارجو ملفات الوحدة الجذر المصرّفة إلى rustc لبناء وحدة مكتبة مصرّفة أو وحدة ثنائية مصرّفة.

لدينا هنا في هذه الحالة حزمة تحتوي src/main.rs فقط، وهذا يعني أنها تحتوي وحدة ثنائية مُصرّفة تُدعى my-project، وفي حالة احتواء المشروع على src/main.rs و src/lib.rs في ذات الوقت فهذا يعني أنه يحتوي على وحدتين مصرفتين، وحدة مكتبة مصرّفة ووحدة ثنائية مُصرّفة ويحتوي كلاهما على الاسم ذاته الخاص بالحزمة، ويمكن أن تحتوي الحزمة عدّة وحدات ثنائية مُصرّفة من خلال وضع الملفات في المجلد src/bin، بحيث يُمثّل كل ملف داخل هذا المجلد وحدة ثنائية مُصرّفة منفصلة. .

تعريف الوحدات للتحكم بالنطاق والخصوصية

سننتقل في هذا القسم للتكلم عن الوحدات modules والأجزاء الأخرى من نظام الوحدة، والتي تُسمى المسارات paths، وهي تسمح لك بتسمية العناصر؛ وسنتطرق أيضًا إلى الكلمة المفتاحية use التي تُضيف مسارًا إلى النطاق؛ والكلمة المفتاحية pub التي تجعل من العناصر عامة public؛ كما سنناقش الكلمة المفتاحية as والحزم الخارجية وعامل glob.

لكن أولًا دعنا نبدأ بمجموعة من القوانين التي تُساعدك بتنظيم شيفرتك البرمجية مستقبلًا، ومن ثمّ سنشرح كل قاعدة من القواعد بالتفصيل.

ورقة مرجعية للوحدات

إليك كيف تعمل كل من المسارات والوحدات وكلمتَي use و pub المفتاحيتَين في المصرّف وكيف يُنظّم المطوّرون شيفرتهم البرمجية. سنستعرض مثالًا عن كلٍ من القواعد الموجودة أدناه وقد ترى هذه الفقرة مفيدةً ويمكن استعمالها مثل مرجع سريع في المستقبل لتذكّرك بكيفية عمل الوحدات.

  • ابدأ من وحدة الجذر المصرّفة root crate: عند تصريف وحدة مصرّفة ما، ينظر المصرّف أولًا إلى ملف وحدة الجذر المُصرّفة (عادةً src/lib.rs لوحدة المكتبة المصرّفة و src/main.rs للوحدة الثنائية المصرّفة).
  • التصريح عن الوحدات: يُمكنك التصريح في ملف وحدة الجذر المصرّفة عن وحدة جديدة باسم معيّن وليكن "garden" بكتابة السطر البرمجي mod garden;‎، وسيبحث عندها المصرّف عن الشيفرة البرمجية داخل الوحدة في هذه الأماكن:
    • ضمن السطر ذاته inline: أي ضمن السطر الخاص بالتعليمة mod garden ضمن الأقواس المعقوصة عوضًا عن الفاصلة المنقوطة.
    • في الملف src/garden.rs.
    • في الملف src/garden/mod.rs.
  • التصريح عن الوحدات الفرعية submodules: يُمكنك التصريح عن وحدات فرعية لأي ملف غير وحدة الجذر المصرّفة، إذ يمكنك مثلًا التصريح عن mod vegtables في الملف src/garden.rs، وسيبحث المصرّف عن شيفرة الوحدة الفرعية في المجلد المُسمى للوحدة الأصل في هذه الأماكن:
    • ضمن السطر ذاته inline: أي ضمن السطر الخاص بالتعليمة mod vegetables ضمن الأقواس المعقوصة عوضًا عن الفاصلة المنقوطة.
    • في الملف src/garden/vegetables.rs.
    • في الملف src/garden/vegetables/mod.rs.
  • المسارات التي ستُشفّر في الوحدات: بمجرد أن تصبح الوحدة جزءًا من الوحدة المصرّفة، يمكنك الرجوع إلى الشيفرة البرمجية الموجودة في تلك الوحدة من أي مكانٍ آخر في نفس الوحدة المصرّفة، طالما تسمح قواعد الخصوصية بذلك، وذلك باستخدام المسار الواصل إلى هذه الشيفرة. يمكنك مثلًا العثور على نوع Asparagus في وحدة الخضار عند المسار crate::garden::vegetables::Asparagus.
  • الخاص private والعام public: الشيفرة البرمجية الموجودة داخل الوحدة هي خاصة بالنسبة للوحدة الأصل افتراضيًا، ولجعل الوحدة عامة يجب أن نصرح عنها باستخدام pub mod بدلًا من mod، ولجعل العناصر الموجودة داخل الوحدة العامة عامة أيضًا نستخدم pub قبل التصريح عنها.
  • الكلمة use المفتاحية: تُنشئ الكلمة المفتاحية use داخل النطاق اختصارًا للعناصر لتجنب استخدام المسارات الطويلة، إذ يمكنك في أي نطاق اختصار المسار crate::garden::vegetables::Asparagus باستخدام use عن طريق كتابة use crate::garden::vegetables::Asparagus;‎ ومن ثمّ يمكنك كتابة Asparagus مباشرةً ضمن النطاق دون الحاجة لكتابة كامل المسار.

إليك وحدة ثنائية مصرّفة اسمها backyard توضح القوانين السابقة. نُسمّي مسار الوحدة المصرّفة أيضًا backyard ويحتوي ذلك المسار على هذه الملفات والمسارات:

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
       └── vegetables.rs
    ├── garden.rs
    └── main.rs

في هذه الحالة، يحتوي ملف وحدة الجذر المصرّفة src/main.rs على التالي:

اسم الملف: src/main.rs

use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
    let plant = Asparagus {};
    println!("I'm growing {:?}!", plant);
}

يعني السطر البرمجي pub mod garden;‎ بأن المصرّف سيضمّ الشيفرة البرمجية التي سيجدها في src/garden.rs، وهي:

اسم الملف: src/garden.rs

pub mod vegetables;

ويعني السطر pub mod vegetables;‎ بأنّ الشيفرة البرمجية الموجودة في الملف src/garden/vegetables.rs ستُتضمّن أيضًا:

#[derive(Debug)]
pub struct Asparagus {}

لننظر إلى ما سبق عمليًا مع بتطبيق القوانين التي ذكرناها سابقًا.

‎تجميع الشيفرات البرمجية المرتبطة ببعضها في الوحدات

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

دعنا نكتب وحدة مكتبة مصرّفة تزوّدنا بخصائص مطعم مثلًا، وسنعرّف داخلها بصمات signatures الدوال، لكن سنترك محتوى كل منها فارغًا لنركّز على جانب تنظيم الشيفرة البرمجية بدلًا من التطبيق الفعلي لمطعم.

يُشار في مجال المطاعم إلى بعض الأجزاء بكونها أجزاء على الواجهة الأمامية front of house بينما يُشار إلى أجزاء أخرى بأجزاء في الخلفية back of house؛ وأجزاء الواجهة الأمامية هي حيث يتواجد الزبائن، أي جانب حجز الزبائن للمقاعد وأخذ الطلبات والمدفوعات منهم وتحضير النادل للمشروبات؛ بينما أجزاء الواجهة هي حيث يتواجد الطهاة وتُطهى الأطباق وتُنظّف الصحون والأعمال الإدارية التي يجريها المدير.

من أجل هيكلة الوحدة المصرّفة بطريقة مماثلة للمطعم، نلجأ لتنظيم الدوال في وحدات متداخلة. لننشئ مكتبةً جديدة ونسميها restaurant بتنفيذ الأمر cargo new restaurant --lib، ثمّ نكتب الشيفرة 1 داخل الملف src/lib.rs لتعريف بعض الوحدات وبصمات الدوال.

اسم الملف: src/lib.rs

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}

        fn seat_at_table() {}
    }

    mod serving {
        fn take_order() {}

        fn serve_order() {}

        fn take_payment() {}
    }
}

[الشيفرة 1: وحدة frontofhouse تحتوي على وحدات أخرى، وتحتوي هذه الوحدات الأخرى بدورها على دوال]

نعرّف الوحدة عن طريق البدء بكتابة الكلمة المفتاحية mod ومن ثم تحديد اسم الوحدة (في حالتنا هذه الاسم هو front_of_house) ونضيف بعد ذلك أقواص معقوصة حول متن الوحدة. يمكننا إنشاء وحدات أخرى داخل وحدة ما وهذه هي الحالة مع الوحدات hosting و serving، يمكن للوحدات أيضًا أن تحتوي داخلها على تعريفات لعناصر أخرى، مثل الهياكل structs والتعدادات enums والثوابت constants والسمات traits أو الدوال كما هو موجود في الشيفرة 1.

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

ذكرنا سابقًا أن src/main.rs و src/lib.rs هي وحدات جذر مُصرَّفة، والسبب في تسميتهما بذلك هو أن محتويات أيّ منهما يشكّل وحدةً تدعى crate في جذر هيكل الوحدة الخاص بالوحدة المصرّفة وكما تُعرف أيضًا بشجرة الوحدة module tree.

توضح الشيفرة 2 شجرة الوحدة الخاصة بهيكل الشيفرة 1.

crate
 └── front_of_house
     ├── hosting
        ├── add_to_waitlist
        └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment

[الشيفرة 2: شجرة الوحدة للشيفرة 1]

توضح الشجرة بأن بعض الوحدات متداخلة مع وحدات أخرى (على سبيل المثال الوحدة hosting متداخلة مع front_of_house)، كما توضح الشجرة أيضًا أن بعض الوحدات أشقاء siblings لبعضها، بمعنى أنها معرفة داخل الوحدة ذاتها (hosting و serving معرفتان داخل front_of_house). لإبقاء تشبيه شجرة العائلة، إذا كانت الوحدة (أ) محتواة داخل الوحدة (ب) نقول بأن الوحدة (أ) هي ابن child للوحدة (ب) وأن الوحدة (ب) هي الأب parent للوحدة (أ). لاحظ أن كامل الشجرة محتواة داخل الوحدة الضمنية المسماة crate.

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

ترجمة -وبتصرف- لقسم من الفصل Managing Growing Projects with Packages, Crates, and Modules من كتاب The Rust Programming Language.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...