يُستخدم المسار path بنفس الطريقة المُستخدمة عند التنقل ضمن نظام الملفات في الحاسوب حتى ندلّ رست على مكان وجود عنصر ما ضمن شجرة الوحدة module tree، وبالتالي علينا معرفة مسار الدالة أولًا إذا أردنا استدعائها.
قد يكون المسار واحدًا من النوعين التاليين:
-
المسار المُطلق absolute path: وهو المسار الكامل بدءًا من جذر الوحدة المصرّفة؛ إذ أن المسار المُطلق لشيفرة برمجية موجودة في وحدة مصرّفة خارجية تبدأ باسم الوحدة المصرّفة بينما يبدأ مسار شيفرة برمجية داخل الوحدة المصرّفة الحالية المُستخدمة بالكلمة
crate
. -
المسار النسبي relative path: ويبدأ هذا المسار من الوحدة المصرّفة الحالية المُستخدمة ويستخدم الكلمة المفتاحية
self
، أوsuper
، أو معرّف ينتمي إلى الوحدة نفسها.
يُتبع كلا نوعَي المسارات السابقَين بمعرّف identifier واحد، أو أكثر ويفصل بينهما نقطتان مزدوجتان ::
.
بالعودة إلى الشيفرة 1 في المقالة السابقة، لنفترض أننا نريد استدعاء الدالة add_to_waitlist
، وهذا يقتضي أن نسأل أنفسنا: ما هو مسار الدالة add_to_waitlist
؟ تحتوي الشيفرة 3 على الشيفرة 1 مع إزالة بعض الوحدات والدوال.
سنستعرض طريقتين لاستدعاء الدالة add_to_waitlist
من دالة جديدة معرّفة في جذر الوحدة المصرّفة وهي eat_at_restaurant
في هذه الحالة، والمسارات الموجودة في كل من الطريقتين صالحة إلا أن هناك مشكلة أخرى ستمنع مثالنا من أن يُصرَّف كما هو، وسنفسّر ذلك قريبًا.
تشكل الدالة eat_at_restaurant
جزءًا من الواجهة البرمجية العامة public API الخاصة بوحدة المكتبة المصرّفة library crate، لذا نُضيف الكلمة المفتاحية pub
إليها، وسنناقش المزيد من التفاصيل بخصوص pub
في الفقرة التالية.
اسم الملف: src/lib.rs
mod front_of_house { mod hosting { fn add_to_waitlist() {} } } pub fn eat_at_restaurant() { // مسار مطلق crate::front_of_house::hosting::add_to_waitlist(); // مسار نسبي front_of_house::hosting::add_to_waitlist(); }
[الشيفرة 3: استدعاء الدالة addtowaitlist باستخدام المسار المُطلق والمسار النسبي]
نستخدم مسارًا مُطلقًا عندما نريد استدعاء الدالة add_to_waitlist
داخل eat_at_restaurant
للمرة الأولى، ويمكننا استخدام المسار المطلق بدءًا بالكلمة المفتاحية crate
بالنظر إلى أن الدالة add_to_waitlist
معرفة في نفس الوحدة المصرّفة الخاصة بالدالة eat_at_restaurant
، ومن ثمّ نضمّن كل من الوحدات الأخرى الموجودة في شجرة الوحدة module tree حتى الوصول إلى الدالة add_to_waitlist
. يمكنك تخيّل هذا الأمر بصورةٍ مشابهة لنظام الملفات على حاسوبك، إذ نكتب المسار /front_of_house/hosting/add_to_waitlist
لتشغيل البرنامج add_to_waitlist
، إلا أننا نستخدم الكلمة المفتاحية crate
للدلالة على جذر الوحدة المصرّفة بدلًا من استخدام /
في بداية المسار، وهو أمرٌ مشابه لكتابة /
في سطر الأوامر حتى تصل إلى جذر نظام الملفات على حاسوبك.
نستخدم المسار النسبي في المرة الثانية التي نستدعي add_to_waitlist
في eat_at_restaurant
، ويبدأ المسار هنا بالاسم front_of_house
، وهو اسم الوحدة المعرفة ضمن نفس مستوى eat_at_restaurant
في شجرة الوحدة، وسيستخدم نظام الملفات المكافئ المسار front_of_house/hosting/add_to_waitlist
بدءًا باسم الوحدة مما يعني أن المسار هو مسار نسبي.
يجب الاختيار بين المسار المطلق والنسبي بناءً على مشروعك ويعتمد على إذا ما كنت ستميل غالبًا إلى نقل شيفرة تعريف العنصر البرمجية وتفريقها عن الشيفرة البرمجية التي تستخدم ذلك العنصر، أو إذا كنت ستبقيهما سويًا. على سبيل المثال، إذا أردنا نقل الوحدة front_of_house
والدالة eat_at_restaurant
إلى وحدة باسم customer_experience
، سنضطر إلى تحديث المسار المطلق الخاص بالدالة add_to_waitlist
، إلا أن المسار سيبقى صالحًا في حال استخدمنا المسار النسبي، لكن إذا نقلنا الدالة eat_at_restaurant
بصورةٍ منفصلة إلى وحدة جديدة تدعى dining
فلن يكون المسار النسبي لاستدعاء الدالة add_to_waitlist
صالحًا بعد الآن ويجب تحديثه، إلا أن المسار المطلق سيبقى صالحًا. نفضّل هنا المسارات المطلقة، لأننا على الأغلب سنحرّك تعريف الشيفرة البرمجية واستدعاء العناصر على نحوٍ متفرق عن بعضهما بعضًا.
دعنا نجرّب تصريف الشيفرة 3 ونقرأ الخطأ ونحاول معرفة السبب في عدم قابلية تصريفها. نحصل على الخطأ الموضح في الشيفرة 4.
$ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant) error[E0603]: module `hosting` is private --> src/lib.rs:9:28 | 9 | crate::front_of_house::hosting::add_to_waitlist(); | ^^^^^^^ private module | note: the module `hosting` is defined here --> src/lib.rs:2:5 | 2 | mod hosting { | ^^^^^^^^^^^ error[E0603]: module `hosting` is private --> src/lib.rs:12:21 | 12 | front_of_house::hosting::add_to_waitlist(); | ^^^^^^^ private module | note: the module `hosting` is defined here --> src/lib.rs:2:5 | 2 | mod hosting { | ^^^^^^^^^^^ For more information about this error, try `rustc --explain E0603`. error: could not compile `restaurant` due to 2 previous errors
[الشيفرة 4: أخطاء المصرف الناجمة عن تصريف الشيفرة 3]
تدلنا رسالة الخطأ على أن الوحدة hosting
هي وحدة خاصة، بمعنى أنه على الرغم من استخدامنا للمسار الصحيح الخاص بوحدة hosting
ودالة add_to_waitlist
إلا أن رست لن تسمح لنا باستخدامهما لأنه لا يوجد لدينا سماحية الوصول إلى هذه الأجزاء الخاصة. جميع العناصر في رست (دوال وتوابع وهياكل وتعدادات ووحدات وثوابت) هي خاصة بالوحدة الأب (الأصل) فقط افتراضيًا، وإذا أردت جعل عنصر ما مثل دالة أو هيكل خاصًا، فعليك وضعه داخل الوحدة.
لا يمكن لعناصر في وحدة أب استخدام العناصر الخاصة داخل الوحدات التابعة لها، إلا أن وحدات الابن يمكن أن تستخدم العناصر الموجودة في الوحدات الأب، وهذا لأن الوحدات الابن تغلّف وتُخفي تفاصيل تطبيقها وتستطيع الوحدات الابن رؤية السياق الخاص بتعريفها، وحتى نستمرّ في تشبيهنا السابق للمطعم، تخيل أن قوانين الخصوصية مشابهة للمكاتب الإدارية للمطعم، فالذي يحصل في هذه المكاتب هو معزول عن زبائن المطعم، لكن يستطيع مدراء المطعم رؤية أي شيء في المطعم.
تختار رست بأن يعمل نظام الوحدة على هذا النحو، بحيث يكون إخفاء تفاصيل التطبيق الداخلي هو الحالة الافتراضية، وبالتالي تستطيع بهذه الطريقة معرفة أي الأجزاء من الشيفرة الداخلية التي يمكنك تغييرها دون التسبب بأخطاء في الشيفرة الخارجية. مع ذلك، تعطيك رست الخيار لكشف الأجزاء الداخلية من الوحدات الابن للوحدات الخارجية باستخدام الكلمة المفتاحية pub
لجعل العنصر عامًا.
كشف المسارات باستخدام الكلمة المفتاحية pub
بالعودة إلى الخطأ الموجود في الشيفرة 4 الذي كان مفاده أن الوحدة hosting
هي وحدة خاصة، نريد أن تمتلك الدالة eat_at_restaurant
الموجودة في الوحدة الأب وصولًا إلى الدالة add_to_waitlist
الموجودة في الوحدة الابن، ولتحقيق ذلك نُضيف الكلمة المفتاحية pub
إلى الوحدة hosting
كما هو موضح في الشيفرة 5.
اسم الملف: src/lib.rs
mod front_of_house { pub mod hosting { fn add_to_waitlist() {} } } pub fn eat_at_restaurant() { // مسار مطلق crate::front_of_house::hosting::add_to_waitlist(); // مسار نسبي front_of_house::hosting::add_to_waitlist(); }
[الشيفرة 5: التصريح عن الوحدة hosting باستخدام الكلمة المفتاحية pub لاستخدامها داخل eatatrestaurant]
لسوء الحظ، تتسبب الشيفرة 5 بخطأ موضح في الشيفرة 6.
$ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant) error[E0603]: function `add_to_waitlist` is private --> src/lib.rs:9:37 | 9 | crate::front_of_house::hosting::add_to_waitlist(); | ^^^^^^^^^^^^^^^ private function | note: the function `add_to_waitlist` is defined here --> src/lib.rs:3:9 | 3 | fn add_to_waitlist() {} | ^^^^^^^^^^^^^^^^^^^^ error[E0603]: function `add_to_waitlist` is private --> src/lib.rs:12:30 | 12 | front_of_house::hosting::add_to_waitlist(); | ^^^^^^^^^^^^^^^ private function | note: the function `add_to_waitlist` is defined here --> src/lib.rs:3:9 | 3 | fn add_to_waitlist() {} | ^^^^^^^^^^^^^^^^^^^^ For more information about this error, try `rustc --explain E0603`. error: could not compile `restaurant` due to 2 previous errors
[شيفرة 6: أخطاء المصرّف الناتجة عن بناء الشيفرة 5]
ما الذي حصل؟ تجعل إضافة الكلمة المفتاحية pub
أمام mod hosting
من الوحدة وحدةً عامةً، وبهذا التغيير إن أمكننا الوصول إلى front_of_house
، فهذا يعني أنه يمكننا الوصول إلى hosting
، ولكن محتوى hosting
ما زال خاصًّا، إذ أن تحويل الوحدة إلى وحدة عامة لا يجعل من محتواها عامًا أيضًا، إذ أن استخدام الكلمة المفتاحية pub
على وحدة ما يسمح الوحدات الأب بالإشارة إلى الشيفرة البرمجية فقط وليس الوصول إلى الشيفرة البرمجية الداخلية، ولأن الوحدات هي بمثابة حاويات فليس هناك الكثير لفعله بجعل الوحدة فقط عامة، وسنحتاج إلى جعل عنصر واحد أو أكثر من عناصرها عامًا أيضًا.
تدلنا الأخطاء الموجودة في الشيفرة 6 إلى أن الدالة add_to_waitlist
هي دالة خاصة، وتُطبَّق قوانين الخصوصية على الهياكل والتعدادات والتوابع والدوال كما هو الحال مع الوحدات.
دعنا نجعل من الدالة add_to_waitlist
دالة عامة بإضافة الكلمة المفتاحية pub
قبل تعريفها كما هو موضح في الشيفرة 7.
اسم الملف: src/lib.rs
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub fn eat_at_restaurant() { // مسار مطلق crate::front_of_house::hosting::add_to_waitlist(); // مسار نسبي front_of_house::hosting::add_to_waitlist(); }
[الشيفرة 7: إضافة الكلمة المفتاحية pub إلى mod hosting و fn addtowaitlist مما يسمح لنا باستدعاء الدالة من eatatrestaurant]
يمكننا الآن تصريف الشيفرة البرمجية بنجاح. دعنا ننظر إلى المسارات المطلقة والمسارات النسبية حتى نفهم لماذا تسمح لنا إضافة pub
باستخدام هذه المسارات في add_to_waitlist
مع مراعاة قوانين الخصوصية.
نبدأ بكتابة crate
في المسار المطلق وهو جذر شجرة الوحدة الخاصة بالوحدة المصرّفة. تُعرّف الوحدة front_of_house
في جذر الوحدة المصرّفة، إلا أنها ليست عامة، وذلك لأن الدالة eat_at_restaurant
معرفة في الوحدة ذاتها الخاصة بالوحدة front_of_house
(أي أن eat_at_restaurant
و front_of_house
أشقاء)، ويُمكننا الإشارة إلى front_of_house
من eat_at_restaurant
. تاليًا تبدو الوحدة hosting
معلّمة marked بفضل الكلمة المفتاحية pub
، إذ يمكننا الوصول إلى الوحدة الأب الخاصة بالوحدة hosting
، وبالتالي يمكننا الوصول إلى hosting
، وأخيرًا، الدالة add_to_waitlist
مُعلّمة بالكلمة المفتاحية pub
، وبالتالي يعمل بقية المسار ويُعد استدعاء الدالة هذا صالحًا.
نعتمد في المسار النسبي نفس المنطق في المسار المطلق باستثناء الخطوة الأولى، إذ بدلًا من البدء بجذر الوحدة المصرّفة، يبدأ المسار من الوحدة front_of_house
، التي تُعرّف في الوحدة ذاتها الخاصة بالوحدة eat_at_restaurant
، وبالتالي يبدأ المسار النسبي من الوحدة التي يعمل عندها تعريف الوحدة eat_at_restaurant
. تبدو الوحدة hosting
و add_to_waitlist
معلّمة بفضل الكلمة المفتاحية pub
، ولذلك يعمل بقية المسار ويُعد استدعاء الدالة هذا صالحًا.
إذا أردت مشاركة وحدة المكتبة المصرّفة الخاصة بك بحيث تستفيد مشاريع أخرى من الشيفرة البرمجية الخاصة بالوحدة المصرّفة، ستكون واجهتك البرمجية API العامة هي نقطة الوصل بين مستخدمي الوحدة المصرّفة ومحتوياتها وهي التي تحدد كيفية تفاعل المستخدمين مع شيفرتك البرمجية، وهناك الكثير من الأشياء التي يجب وضعها في الحسبان لإدارة التغييرات التي تجريها على الواجهة البرمجية العامة حتى تجعل عملية الاعتماد على وحدتك المصرّفة أسهل بالنسبة للمستخدمين، إلا أن هذه الأشياء خارج نطاق موضوعنا هنا، وإذا كنت مهتمًا بهذا الخصوص عليك رؤية دليل رست الخاصة بالواجهات البرمجية.
الممارسات المثلى للحزم التي تحتوي على وحدات ثنائية مصرفة ووحدات مكتبة مصرفة
ذكرنا أنه يمكن أن تحتوي الحزمة على كلٍّ من جذر وحدة ثنائية مصرّفة src/main.rs إضافةً إلى جذر وحدة مكتبة مصرّفة src/lib.rs وأن يحمل كلتا الوحدتين المصرّفتين اسم الحزمة افتراضيًا، ويحتوي هذا النوع من الحزم عادةً على شيفرة برمجية كافية داخل الوحدة الثنائية المصرّفة، بحيث يمكن إنشاء ملف تنفيذي باستخدامها يستدعي الشيفرة البرمجية الموجودة في وحدة المكتبة المصرّفة، مما يسمح للمشاريع الأخرى بالاستفادة القصوى من المزايا التي تقدمها هذه الحزمة وذلك لأنه من الممكن مشاركة الشيفرة البرمجية الموجودة داخل وحدة المكتبة المصرّفة.
يجب أن تُعرَّف شجرة الوحدة في الملف src/lib.rs، ومن ثمّ يمكننا استخدام أي عنصر عام في الوحدة الثنائية المُصرّفة ببدء المسار باسم الحزمة، وبذلك تصبح الوحدة الثنائية المصرّفة مستخدمًا user لوحدة المكتبة المصرّفة كما تستخدم أية وحدة مصرّفة خارجية وحدة مكتبة مصرّفة عادةً، وذلك باستخدام الواجهة البرمجية العامة فقط. يساعدك ذلك في تصميم واجهة برمجية جيّدة؛ إذ أنك لست كاتب المكتبة فقط في هذه الحالة، بل أنك مستخدمها أيضًا.
سنستعرض الممارسات الشائعة في تنظيم برامج سطر الأوامر التي تحتوي على كل من الوحدة الثنائية المصرّفة ووحدة المكتبة المصرّفة لاحقًا.
كتابة المسارات النسبية باستخدام super
يُمكننا إنشاء مسار نسبي يبدأ في الوحدة الأب بدلًا من الوحدة الحالية أو جذر وحدة مصرّفة باستخدام الكلمة المفتاحية super
في بداية المسار، وهذا يُشابه بدء مسار الملفات بالنقطتين ..
، إذ يسمح لنا استخدام super
بالإشارة إلى عنصر نعلم أنه موجود في الوحدة الأب، مما يجعل إعادة ترتيب شجرة الوحدة أسهل عندما تكون الوحدة مرتبطة بصورةٍ وثيقة مع الوحدة الأب مع احتمال نقل الوحدة الأب إلى مكان آخر ضمن شجرة الوحدة في يوم ما.
ألقِ نظرةً إلى الشيفرة 8 التي تصف موقفًا معينًا، ألا وهو تصحيح الطاهي لطلب غير صحيح وإحضاره بنفسه شخصيًا إلى الزبون. تستدعي الدالة fix_incorrect_order
المُعرفة في الوحدة back_of_house
الدالة deliver_order
المعرّفة في الوحدة الأب بتحديد المسار إلى الدالة deliver_order
باستخدام الكلمة المفتاحية super
في البداية:
اسم الملف: src/lib.rs
fn deliver_order() {} mod back_of_house { fn fix_incorrect_order() { cook_order(); super::deliver_order(); } fn cook_order() {} }
[الشيفرة 8: استدعاء دالة باستخدام المسار النسبي بدءًا بالكلمة super]
تتواجد الدالة fix_incorrect_order
في الوحدة back_of_house
، لذا يمكننا استخدام super
للإشارة إلى الوحدة الأب الخاصة بالوحدة back_of_house
التي هي في هذه الحالة الوحدة crate
الجذر، ومن هناك نبحث عن deliver_order
ونجده بنجاح. نعتقد هنا أن الوحدة back_of_house
والدالة deliver_order
سيبقيان سويًا نظرًا للعلاقة فيما بينهما مهما تغيّر تنظيم شجرة الوحدة، وبالتالي استخدمنا super
بحيث نختصر على أنفسنا أماكن تحديث الشيفرة البرمجية في المستقبل إذا قرّرنا نقل الشيفرة البرمجية إلى وحدة مختلفة.
جعل الهياكل والتعدادات عامة
يمكننا أيضًا استخدام الكلمة المفتاحية pub
لتعيين الهياكل structs والتعدادات enums على أنها عامة، إلا أن هناك بعض التفاصيل الإضافية لاستخدام pub
مع الهياكل والتعدادات؛ فإذا استخدمنا pub
قبل تعريف الهيكل، فسيتسبب هذا بإنشاء هيكل عام إلا أن حقول هذا الهيكل ستظلّ خاصة، ويمكن جعل كل حقل عامًا أو لا اعتمادًا على أسس وشروط كل حالة بحد ذاتها. نُعرّف في الشيفرة 9 هيكلًا عامًا باسم back_of_house::Breakfast
بحقل عام toast
وحقل خاص seasonal_fruit
، يُحاكي هذا الأمر عملية اختيار الزبون لنوع الخبز الذي يأتي مع الوجبة واختيار الطاهي لنوع الفاكهة الذي يأتي مصحوبًا مع الوجبة بناءً على المتاح في مخزون المطعم؛ ولأن الفاكهة المُتاحة تتغير سريعًا حسب المواسم فالزبون لا يستطيع اختيار الفاكهة أو حتى رؤية النوع الذي سيحصل عليه.
اسم الملف: src/lib.rs
mod back_of_house { pub struct Breakfast { pub toast: String, seasonal_fruit: String, } impl Breakfast { pub fn summer(toast: &str) -> Breakfast { Breakfast { toast: String::from(toast), seasonal_fruit: String::from("peaches"), } } } } pub fn eat_at_restaurant() { // Rye طلب فطور في الصيف مع خبز محمص من النوع راي let mut meal = back_of_house::Breakfast::summer("Rye"); // غيّرنا رأينا بخصوص الخبز الذي نريده meal.toast = String::from("Wheat"); println!("I'd like {} toast please", meal.toast); // لن يُصرَّف السطر التالي إذا أزلنا تعليقه لأنه من غير المسموح رؤية أو تعدي الفاكهة الموسمية التي تأتي مع الوجبة // meal.seasonal_fruit = String::from("blueberries"); }
[الشيفرة 9: هيكل يحتوي على بعض الحقول العامة والخاصة]
بما أن الحقل toast
في الهيكل back_of_house::Breakfast
هو حقل عام، فهذا يعني أننا نستطيع الكتابة إلى والقراءة من الحقل toast
في eat_at_restaurant
باستخدام النقطة. لاحظ أنه لا يمكننا استخدام الحقل seasonal_fruit
في eat_at_restaurant
وذلك لأن الحقل seasonal_fruit
هو حقل خاص. حاول إزالة تعليق السطر البرمجي الذي يُعدّل من قيمة الحقل seasonal_fruit
وستحصل على خطأ.
لاحظ أن الهيكل back_of_house::Breakfast
بحاجة لتوفير دالة عامة مرتبطة به تُنشئ نسخةً من Breakast
(سميناها summer
في هذا المثال)، لأنه يحتوي على حقل خاص، ولن يصبح بالإمكان إنشاء نسخة من الهيكل Breakfast
في eat_at_restaurant
إذا لم يحتوي على دالة مشابهة، وذلك لأنه لن يكون بإمكاننا ضبط قيمة للحقل الخاص seasonal_fruit
في eat_at_restaurant
.
وفي المقابل، إذا أنشأنا تعدادًا عامًا، ستكون جميع متغايراته variants عامة، ونحن بحاجة لاستخدام الكلمة المفتاحية pub
مرةً واحدةً فقط قبل الكلمة enum
كما هو موضح في الشيفرة 10.
اسم الملف: src/lib.rs
mod back_of_house { pub enum Appetizer { Soup, Salad, } } pub fn eat_at_restaurant() { let order1 = back_of_house::Appetizer::Soup; let order2 = back_of_house::Appetizer::Salad; }
[الشيفرة 10: تحديد المعدد على أنه عام يجعل جميع متغايراته عامة]
يمكننا استخدام المتغايرات Soup
و Salad
في eat_at_restaurant
وذلك لأننا أنشأنا التعداد Appetier
على أنه تعداد عام.
ليست التعدادات مفيدة إلا إذا كانت جميع متغايراتها عامة، وسيكون من المزعج تحديد كل متغاير من متغايرات المعدد بكونه متغاير علني باستخدام pub
، لذا فإن حالة متغايرات التعداد الافتراضية هي أن تكون عامة. على النقيض، يمكن أن تكون الهياكل مفيدة دون أن يكون أحد حقولها عامًا وبالتالي تتبع الهياكل القاعدة العامة التي تنص على أن جميع العناصر خاصة إلا إذا أُشير إلى العنصر على أنه عام باستخدام pub
.
هناك بعض الحالات الأخرى بالتي تتضمن pub
التي لم نناقشها بعد، ألا وهي آخر مزايا نظام الوحدات: الكلمة المفتاحية use
، والتي سنغطّيها تاليًا، ومن ثمّ سنناقش كيفية دمج pub
و use
معًا.
ترجمة -وبتصرف- لقسم من الفصل Managing Growing Projects with Packages, Crates, and Modules من كتاب The Rust Programming Language.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.