التحكم في النطاق والخصوصية باستخدام الوحدات البرمجية
في هذا القسم، سنتحدث عن الوحدات البرمجية (modules) وأجزاء أخرى من نظام الوحدات البرمجية (module system)، وتحديداً المسارات (paths)، التي تسمح لك بتسمية العناصر؛ والكلمة المفتاحية use التي تجلب المسار (path) إلى النطاق (scope)؛ والكلمة المفتاحية pub لجعل العناصر عامة (public). سنناقش أيضاً الكلمة المفتاحية as والطرود الخارجية (external packages) وعامل الشمول (glob operator).
ورقة غش الوحدات البرمجية
قبل أن ننتقل إلى تفاصيل modules و paths، نقدم هنا مرجعاً سريعاً حول كيفية عمل modules و paths والكلمة المفتاحية use والكلمة المفتاحية pub في المترجم (compiler)، وكيف ينظم معظم المطورين كودهم. سنمر بأمثلة على كل من هذه القواعد طوال هذا الفصل، ولكن هذا مكان رائع للرجوع إليه كتذكير بكيفية عمل modules.
- البدء من جذر الكريت (crate root): عند تصريف (compiling) كريت (crate)، يبحث compiler أولاً في ملف crate root (عادةً ما يكون src/lib.rs لكريت المكتبة و src/main.rs لكريت ثنائي) عن كود لتصريفه.
- التصريح عن الوحدات البرمجية (Declaring modules): في ملف crate root، يمكنك التصريح عن modules جديدة؛ لنفترض أنك صرحت عن وحدة “garden” باستخدام
mod garden;. سيبحث compiler عن كود الوحدة في هذه الأماكن:- مضمناً (Inline)، داخل أقواس متعرجة تحل محل الفاصلة المنقوطة التي تلي
mod garden - في الملف src/garden.rs
- في الملف src/garden/mod.rs
- مضمناً (Inline)، داخل أقواس متعرجة تحل محل الفاصلة المنقوطة التي تلي
- التصريح عن الوحدات الفرعية (Declaring submodules): في أي ملف آخر غير crate root، يمكنك التصريح عن وحدات فرعية (submodules). على سبيل المثال، قد تصرح عن
mod vegetables;في src/garden.rs. سيبحث compiler عن كود submodule داخل المجلد المسمى باسم الوحدة الأب (parent module) في هذه الأماكن:- مضمناً، مباشرة بعد
mod vegetablesداخل أقواس متعرجة بدلاً من الفاصلة المنقوطة - في الملف src/garden/vegetables.rs
- في الملف src/garden/vegetables/mod.rs
- مضمناً، مباشرة بعد
- المسارات إلى الكود في الوحدات البرمجية: بمجرد أن تصبح module جزءاً من crate الخاص بك، يمكنك الرجوع إلى الكود في تلك module من أي مكان آخر في نفس crate، طالما تسمح قواعد الخصوصية (privacy rules) بذلك، باستخدام path المؤدي إلى الكود. على سبيل المثال، سيتم العثور على النوع
Asparagusفي وحدة vegetables الخاصة بـ garden فيcrate::garden::vegetables::Asparagus. - خاص مقابل عام (Private vs. public): الكود داخل module يكون خاصاً (private) عن وحداته الأب بشكل افتراضي. لجعل module عامة (public)، صرح عنها باستخدام
pub modبدلاً منmod. لجعل العناصر داخل public module عامة أيضاً، استخدمpubقبل التصريح عنها. - الكلمة المفتاحية
use: داخل scope، تنشئ الكلمة المفتاحيةuseاختصارات للعناصر لتقليل تكرار paths الطويلة. في أي scope يمكنه الرجوع إلىcrate::garden::vegetables::Asparagusيمكنك إنشاء اختصار باستخدامuse crate::garden::vegetables::Asparagus;ومنذ ذلك الحين فصاعداً ستحتاج فقط إلى كتابةAsparagusلاستخدام ذلك النوع في scope.
هنا، ننشئ binary crate باسم backyard يوضح هذه القواعد. يحتوي مجلد crate، المسمى أيضاً backyard، على هذه الملفات والمجلدات:
backyard
├── Cargo.lock
├── Cargo.toml
└── src
├── garden
│ └── vegetables.rs
├── garden.rs
└── main.rs
ملف crate root في هذه الحالة هو 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، وهو:
pub mod vegetables;
هنا، تعني pub mod vegetables; أن الكود في src/garden/vegetables.rs مضمن أيضاً. هذا الكود هو:
#[derive(Debug)]
pub struct Asparagus {}
الآن دعونا ندخل في تفاصيل هذه القواعد ونستعرضها عملياً!
تجميع الكود ذو الصلة في وحدات برمجية
تسمح لنا Modules بتنظيم الكود داخل crate لسهولة القراءة وإعادة الاستخدام. تسمح لنا modules أيضاً بالتحكم في خصوصية العناصر لأن الكود داخل module يكون private بشكل افتراضي. العناصر الخاصة هي تفاصيل تنفيذ داخلية (internal implementation details) غير متاحة للاستخدام الخارجي. يمكننا اختيار جعل modules والعناصر داخلها public، مما يكشفها للسماح للكود الخارجي باستخدامها والاعتماد عليها.
كمثال، لنكتب library crate يوفر وظائف مطعم. سنحدد تواقيع الدوال (function signatures) ولكن سنترك أجسامها فارغة للتركيز على تنظيم الكود بدلاً من تنفيذ (implementation) المطعم.
في صناعة المطاعم، يشار إلى بعض أجزاء المطعم باسم “واجهة المطعم” (front of house) وأجزاء أخرى باسم “خلفية المطعم” (back of house). Front of house هو المكان الذي يتواجد فيه الزبائن؛ وهذا يشمل المكان الذي يجلس فيه المضيفون الزبائن، ويأخذ فيه النادلون الطلبات والمدفوعات، ويقوم فيه السقاة بإعداد المشروبات. Back of house هو المكان الذي يعمل فيه الطهاة في المطبخ، ويقوم فيه غاسلو الأطباق بالتنظيف، ويقوم فيه المديرون بالعمل الإداري.
لهيكلة crate الخاص بنا بهذه الطريقة، يمكننا تنظيم دواله في modules متداخلة. أنشئ مكتبة جديدة باسم restaurant عن طريق تشغيل cargo new restaurant --lib. ثم أدخل الكود الموجود في القائمة 7-1 في src/lib.rs لتعريف بعض modules وتواقيع الدوال؛ هذا الكود هو قسم front of house.
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() {}
}
}
نحدد module باستخدام الكلمة المفتاحية mod متبوعة باسم module (في هذه الحالة، front_of_house). ثم يوضع جسم module داخل أقواس متعرجة. داخل modules، يمكننا وضع modules أخرى، كما في هذه الحالة مع الوحدتين hosting و serving. يمكن أن تحتوي modules أيضاً على تعريفات لعناصر أخرى، مثل الهياكل (structs) والتعدادات (enums) والثوابت (constants) والسمات (traits) وكما في القائمة 7-1، الدوال (functions).
باستخدام modules، يمكننا تجميع التعريفات ذات الصلة معاً وتسمية سبب ارتباطها. يمكن للمبرمجين الذين يستخدمون هذا الكود التنقل فيه بناءً على المجموعات بدلاً من الاضطرار إلى قراءة جميع التعريفات، مما يسهل العثور على التعريفات ذات الصلة بهم. سيعرف المبرمجون الذين يضيفون وظائف جديدة إلى هذا الكود مكان وضع الكود للحفاظ على تنظيم البرنامج.
ذكرنا سابقاً أن src/main.rs و src/lib.rs يسمى كل منهما crate roots. والسبب في تسميتهما هو أن محتويات أي من هذين الملفين تشكل module تسمى crate في جذر هيكل الوحدات البرمجية للكريت، والمعروف باسم شجرة الوحدات البرمجية (module tree).
توضح القائمة 7-2 module tree للهيكل الموجود في القائمة 7-1.
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
توضح هذه الشجرة كيف تتداخل بعض modules داخل modules أخرى؛ على سبيل المثال، hosting يتداخل داخل front_of_house. توضح الشجرة أيضاً أن بعض modules هي أشقاء (siblings)، مما يعني أنها معرفة في نفس module؛ hosting و serving هما siblings معرفان داخل front_of_house. إذا كانت الوحدة A محتواة داخل الوحدة B، فإننا نقول إن الوحدة A هي الابن (child) للوحدة B وأن الوحدة B هي الأب (parent) للوحدة A. لاحظ أن module tree بالكامل متجذرة تحت module الضمنية المسماة crate.
قد تذكرك module tree بشجرة مجلدات نظام الملفات على جهاز الكمبيوتر الخاص بك؛ هذا تشبيه دقيق للغاية! تماماً مثل المجلدات في نظام الملفات، تستخدم modules لتنظيم كودك. وتماماً مثل الملفات في المجلد، نحتاج إلى طريقة للعثور على modules الخاصة بنا.