قد تكون عملية كتابة مسارات استدعاء الدوال في بعض الأحيان عملية غير مريحة ورتيبة، كان علينا في الشيفرة 7 (سابقًا) تحديد front_of_house
و hosting
في حال اخترنا المسار النسبي relative path أو المسار المطلق absolute path إلى الدالة add_to_waitlist
وفي كل مرة أردنا استدعاء الدالة add_to_waitlist
أيضًا. لحسن الحظ هناك طريقة لتبسيط العملية السابقة، إذ يمكننا إنشاء اختصار للمسار باستخدام الكلمة المفتاحية use
مرةً واحدةً فقط ومن ثمّ استخدام المسار الأقصر المُختصر في كل مكان ضمن النطاق.
نُضيف الوحدة crate::front_of_house::hosting
إلى نطاق دالة eat_at_restaurant
في الشيفرة 11، وذلك حتى نكتفي بكتابة hosting::add_to_waitlist
لاستدعاء الدالة add_to_waitlist
في eat_at_restaurant
.
اسم الملف: src/lib.rs
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }
الشيفرة 11: إضافة الوحدة إلى النطاق باستخدام use
تُعد عملية إضافة use
متبوعةً بالمسار في النطاق عمليةً مشابهةً لإنشاء رابط رمزي symbolic link في نظام الملفات، إذ يصبح hosting
اسمًا صالحًا بإضافة use crate::front_of_house::hosting
في جذر الوحدة المصرّفة وضمن ذلك النطاق وكأن الوحدة hosting
معرفةٌ داخل جذر الوحدة المصرّفة. تتبع المسارات الموجودة ضمن النطاق باستخدام use
قوانين الخصوصية مثلها مثل أي مسار آخر.
لاحظ أن use
يُنشئ اختصارًا للنطاق الذي يحتوي على use
فقط. ننقل في الشيفرة 12 الدالة eat_at_restaurant
إلى وحدة ابن جديدة تدعى customer
وهي ذات نطاق مختلف عن تعليمة use
، لذا لن نستطيع تصريف محتوى الدالة:
اسم الملف: src/lib.rs
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use crate::front_of_house::hosting; mod customer { pub fn eat_at_restaurant() { hosting::add_to_waitlist(); } }
الشيفرة 12: تعليمة use تُطبّق فقط في النطاق الواردة فيها
يوضح خطأ التصريف التالي أن الاختصار غير موجود ضمن الوحدة customer
:
$ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant) error[E0433]: failed to resolve: use of undeclared crate or module `hosting` --> src/lib.rs:11:9 | 11 | hosting::add_to_waitlist(); | ^^^^^^^ use of undeclared crate or module `hosting` warning: unused import: `crate::front_of_house::hosting` --> src/lib.rs:7:5 | 7 | use crate::front_of_house::hosting; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default For more information about this error, try `rustc --explain E0433`. warning: `restaurant` (lib) generated 1 warning error: could not compile `restaurant` due to previous error; 1 warning emitted
لاحظ وجود تحذير بخصوص عدم وجود use
في النطاق، ولتصحيح هذه المشكلة ننقل التعليمة use
إلى الوحدة customer
أيضًا، أو نُشير إلى الاختصار في الوحدة الأب باستخدام super::hosting
ضمن الوحدة الابن customer
.
إنشاء مسارات اصطلاحية Idiomatic باستخدام use
لعلّك تساءلت عند قراءتك للشيفرة 11 عن سبب كتابتنا use crate::front_of_house::hosting
ومن ثم استدعائنا hosting::add_to_waitlist
في eat_at_restaurant
بدلًا من تحديد مسار use
الموجود في الدالة add_to_waitlist
لتحقيق النتيجة ذاتها كما هو الأمر في الشيفرة 13.
اسم الملف: src/lib.rs
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use crate::front_of_house::hosting::add_to_waitlist; pub fn eat_at_restaurant() { add_to_waitlist(); }
الشيفرة 13: إضافة الدالة add_to_waitlist إلى النطاق باستخدام use وهو استخدام غير اصطلاحي unidiomatic
على الرغم من أن الشيفرة 11 والشيفرة 13 يحققان الغرض ذاته، إلا أن الشيفرة 11 هي الطريقة الاصطلاحية لإضافة دالة إلى النطاق باستخدام use
. تعني إضافة وحدة الدالة الأب إلى النطاق باستخدام use
أننا بحاجة تحديد الوحدة الأب عند استدعاء الدالة، وتحديد الوحدة الأب عند استدعاء الدالة يوضّح أن الدالة ليست معرّفة محليًا مع المحافظة على تقليل تكرار كامل المسار. لا توضح الشيفرة البرمجية الموجودة في الشيفرة 13 مكان تعريف add_to_waitlist
.
على الجانب الآخر، عند إضافة الهياكل والتعدادات والعناصر الأخرى إلى النطاق باستخدام use
فإن الاستخدام الاصطلاحي هو تحديد كامل المسار، وتوضح الشيفرة 14 الطريقة الاصطلاحية في إضافة الهيكل HashMap
الموجود في المكتبة القياسية إلى نطاق الوحدة الثنائية المُصرّفة.
اسم الملف: src/main.rs
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
الشيفرة 14: إضافة HashMap إلى النطاق بطريقة اصطلاحية
لا يوجد هناك أي مبرر قوي بخصوص هذا الاصطلاح سوى أنه الاصطلاح المتعارف عليه واعتاد معظم المبرمجين عليه، وبالتالي فإن استخدامه يجعل قراءة شيفرة راست البرمجية والتعديل عليها أسهل.
الاستثناء لهذا الاصطلاح هو حالة إضافة عنصرين إلى النطاق يحملان الاسم ذاته باستخدام تعليمة use
، إذ لا تسمح رست بذلك. توضح الشيفرة 15 كيفية إضافة نوعَي Result
إلى النطاق يحملان الاسم ذاته ولكنهما ينتميان إلى وحدات أب مختلفة، إضافةً إلى كيفية الإشارة إليهما.
اسم الملف: src/lib.rs
use std::fmt; use std::io; fn function1() -> fmt::Result { // --snip-- Ok(()) } fn function2() -> io::Result<()> { // --snip-- Ok(()) }
الشيفرة 15: إضافة نوعين من الاسم ذاته إلى النطاق ذاته يتطلب استخدام الوحدات الأب
كما تلاحظ، يميز استخدام الوحدات الأب ما بين النوعين Result
، وإذا استخدمنا use std::fmt::Result
و use std::io::Result
بدلًا من ذلك فسيكون لدينا قيمتين Result
في نفس النطاق، ولن تعرف رست عندها أي الأنواع التي نقصدها عندما نستخدم Result
.
إضافة أسماء جديدة باستخدام الكلمة المفتاحية as
هناك حل آخر لمشكلة إضافة نوعين من الاسم ذاته إلى النطاق باستخدام use
، إذ يمكننا كتابة الكلمة المفتاحية as
بعد المسار وكتابة اسم محلي بعدها أو اسم مستعار alias للنوع. توضح الشيفرة 16 طريقةً أخرى لكتابة الشيفرة 15 بإعادة تسمية واحد من النوعَين Result
باستخدام as
.
اسم الملف: src/lib.rs
use std::fmt::Result; use std::io::Result as IoResult; fn function1() -> Result { // --snip-- Ok(()) } fn function2() -> IoResult<()> { // --snip-- Ok(()) }
الشيفرة 16: إعادة تسمية نوع بعد إضافته إلى النطاق باستخدام الكلمة المفتاحية as
اخترنا في تعليمة use
الثانية الاسم الجديد IoResult
للنوع std::io::Result
وهو اسم لا يتعارض مع الاسم Result
في std::fmt
الذي أضفناه إلى النطاق أيضًا. تعدّ كلًا من الشيفرة 15 والشيفرة 16 طريقةً اصطلاحيةً في التغلب على مشكلة تعارض الأسماء، فاختر ما يحلو لك.
إعادة تصدير الأسماء باستخدام pub use
عندما نُضيف اسمًا إلى النطاق باستخدام الكلمة المفتاحية use
، سيكون هذا الاسم المتاح في النطاق الجديد خاصًا. لتمكين الشيفرة البرمجية التي تستدعي الشيفرة البرمجية لتُشير إلى ذلك الاسم وكأنه معرّف في نطاق الشيفرة البرمجية تلك، علينا استخدام pub
مع use
، وتُعرف هذه الطريقة باسم إعادة التصدير re-exporting لأننا نُضيف العنصر إلى النطاق، لكننا نجعله ممكن الإضافة لنطاقات أخرى.
تمثل الشيفرة 17 نسخةً معدلةً عن الشيفرة 11 بتغيير استخدام use
في الوحدة الجذر إلى pub use
.
اسم الملف: src/lib.rs
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }
الشيفرة 17: جعل الاسم متاحًا لأي شيفرة برمجية لاستخدامه من نطاق جديد باستخدام pub use
كان على الشيفرة البرمجية الخارجية -قبل هذا التغيير- استدعاء الدالة add_to_waitlist
باستخدام المسار restaurant:: front_of_house::hosting::add_to_waitlist()
، والآن وبعد إعادة تصدير وحدة hosting
باستخدام pub use
من الوحدة الجذر يُمكن للشيفرة الخارجية الآن أن تستخدم المسار restaurant::hosting::add_to_waitlist()
.
إعادة التصدير مفيد عندما يكون الهيكل الداخلي لشيفرتك البرمجية مختلفًا عن الطريقة التي قد يفكّر بها المبرمجون الآخرون باستدعاء شيفرتك البرمجية بحسب استخدامهم لها. على سبيل المثال وبالنظر إلى تشبيه المطعم، يفكر الأشخاص الذين يعملون بالمطعم بخصوص كل من الأشياء التي تحدث في واجهة المطعم وفي خلفية المطعم، إلا أن الزبائن الذي يزورون المطعم لن يفكرون غالبًا بخصوص الأشياء التي تحدث في خلفية المطعم ضمن هذا السياق. يمكننا كتابة شيفرتنا البرمجية بالاستعانة بالكلمتين pub use
ضمن هيكل واحد مع كشف expose هيكل مختلف، وفعل ذلك يجعل من مكتبتنا منظّمة للمبرمجين الذي يعملون على المكتبة وللمبرمجين الذين يستدعون المكتبة على حد سواء، وسننظر لمثال آخر يحتوي على pub use
وكيف يؤثر على توثيق الوحدة المصرّفة لاحقًا.
استخدام الحزم الخارجية
برمجنا سابقًا لعبة تخمين الأرقام واستخدمنا فيها حزمةً خارجيةً تدعى rand
للحصول على أرقام عشوائية، ولاستخدام rand
في مشروعنا أضفنا السطر التالي إلى ملف Cargo.toml:
اسم الملف: Cargo.toml
rand = "0.8.3"
تخبر إضافة rand
مثل اعتمادية dependency في ملف Cargo.toml كارجو بأنه يجب عليه تنزيل الحزمة rand
وأي اعتمادية من crates.io وجعل الحزمة rand
متاحة في مشروعنا.
أضفنا السطر use
مع اسم الوحدة المُصرّفة لجلب التعاريف الموجودة في الحزمة rand
إلى نطاق حزمتنا، وأضفنا العناصر التي أردنا إضافتها إلى النطاق. تذكر أننا أضفنا السمة Rng
إلى نطاقنا واستدعينا الدالة rand::thread_rng
:
use rand::Rng; fn main() { let secret_number = rand::thread_rng().gen_range(1..=100); }
كتب الكثير من أعضاء مجتمع رست حزمًا وجعلها متاحة على crates.io، ويجب أن تتبع هذه الخطوات إذا أردت استخدام أي منها: إضافة الحزم في الملف Cargo.toml مثل قائمة، واستخدام use
لإضافة العناصر من وحداتها المُصرّفة إلى النطاق.
لاحظ أن المكتبة القياسية std
هي في الحقيقة وحدة مُصرَّفة خارجية لحزمتنا، وليس علينا تغيير الملف Cargo.toml لتضمين std
لأن المكتبة القياسية تُضاف افتراضيًا إلى مشروعنا بلغة رست، إلا أننا بحاجة إضافة عناصرها إلى نطاق حزمتنا باستخدام use
. على سبيل المثال، يجب علينا كتابة السطر البرمجي التالي لاستخدام HashMap
:
use std::collections::HashMap;
وهذا يمثّل مسار مطلق يبدأ بالكلمة std
وهو اسم وحدة مكتبة مُصرَّفة قياسي.
استخدام المسارات المتداخلة لتنظيم تعليمات use الطويلة
إذا كنا نستخدم عدة عناصر معرفة في الوحدة المصرّفة ذاتها أو الوحدة ذاتها، فكتابة كل واحدة منها على حدى في سطر خاص سيأخذ الكثير من المساحة ضمن ملفنا. على سبيل المثال انظر إلى تعليمات use
التالية التي استخدمناها في برمجة لعبة التخمين التي تُضيف بعض العناصر من std
إلى النطاق:
اسم الملف: src/main.rs
use std::cmp::Ordering; use std::io;
عوضًا عن ذلك يمكننا استخدام المسارات المتداخلة nested paths لإضافة العناصر ذاتها إلى النطاق ضمن سطر واحد، ونفعل ذلك عن طريق تحديد الجزء المشترك من المسار متبوعًا بنقطتين مزدوجتين ومن ثم أقواس معقوصة حول لائحة من الأجزاء التي تختلف عن الجزء المشترك من المسار كما هو موضح في الشيفرة 18.
اسم الملف: src/main.rs
use std::{cmp::Ordering, io};
الشيفرة 18: تحديد مسار متداخل لإضافة عدة عناصر إلى النطاق باستخدام مسار مشترك
يُقلّل استخدام المسارات المشتركة عدد السطور اللازمة لإضافة بعض العناصر باستخدام تعليمة use
في البرامج الكبيرة بصورةٍ ملحوظة.
يمكننا استخدام المسار المتداخل ضمن أي مستوى في المسار، وهو أمر مفيد عند دمج تعليمتَي use
تتشاركان بمسار جزئي. توضح الشيفرة 19 مثلًا تعليمتَي use
: أحدها تُضيف std::io
إلى النطاق والأخرى تجلب std::io::Write
إلى النطاق.
اسم الملف: src/lib.rs
use std::io; use std::io::Write;
الشيفرة 19: تعليمتَي use، تشكًل واحدة منهما مسارًا جزئيًا للآخر
الجزء المشترك من المسارَين هو std::io
وهذا هو المسار الكامل الأول، ولدمج المسارَين إلى تعليمة use
واحدة يمكننا استخدام self
في المسار المتداخل كما هو موضح في الشيفرة 20.
اسم الملف: src/lib.rs
use std::io::{self, Write};
الشيفرة 20: دمج المسارَين في الشيفرة 19 إلى تعليمة use واحدة
يُضيف السطر البرمجي السابق كلًا من std::io
و std::io::Write
إلى النطاق.
عامل تحديد الكل glob
يمكننا استخدام العامل *
إذا أردنا إضافة جميع العناصر العلنية الموجودة في مسار ما إلى النطاق:
use std::collections::*;
تُضيف تعليمة use
السابقة جميع العناصر العلنية المعرفة في المسار std::collections
إلى النطاق الحالي. كُن حريصًا عن استخدامك لهذا العامل، لأنه قد يجعل من الصعب معرفة أي الأسماء الموجودة في النطاق وأين الأسماء المستخدمة في البرنامج والمعرفة داخله.
يُستخدم عامل تحديد الكل غالبًا عند تجربة إضافة كل شيء ضمن عملية تجربة إلى الوحدة tests
، وسنتكلم لاحقًا عن كيفية كتابة هذا النوع من الوحدات. يُستخدم عامل تحديد الكل أيضًا في بعض الأحيان جزءًا من النمط التمهيدي prelude pattern، ألقِ نظرةً على توثيق المكتبة القياسية لمزيد من التفاصيل حول هذا النمط.
فصل الوحدات إلى ملفات مختلفة
لحدّ اللحظة، عرّفت الشيفرات البرمجية التي استعرضناها في الأمثلة عدة وحدات في ملف واحد، إلا أنك قد تكون بحاجة نقل تعاريف الوحدات إلى ملف منفصل في حال كان عدد الوحدات كبيرًا وذلك بهدف جعل شيفرتك البرمجية سهلة القراءة.
على سبيل المثال، دعنا نبدأ من الشيفرة البرمجية الموجودة في الشيفرة 17 التي تحتوي على عدّة وحدات للمطاعم، وسنستخرج هذه الوحدات إلى ملف مخصص بدلًا من تعريف كل من الوحدات في ملف جذر الوحدة المُصرّفة، وفي هذه الحالة ملف جذر الوحدة المُصرّفة هو src/lib.rs إلا أن هذه العملية تعمل أيضًا مع الوحدات الثنائية المُصرّفة التي يكون ملف ملف جذر الوحدة المُصرّفة الخاص بها هو src/main.rs.
دعنا نستخرج أولًا الوحدة front_of_house
إلى ملفها الخاص، ونحذف الشيفرة البرمجية داخل الأقواس المعقوصة الخاصة بالوحدة front_of_house
ونترك فقط تصريح ;mod front_of_house
، بحيث يحتوي src/lib.rs على الشيفرة البرمجية الموضحة في الشيفرة 21، ولاحظ أن الشيفرة لن تُصرَّف حتى نُنشئ ملف src/front_of_house.rs، وهو ما سنفعله في الشيفرة 22.
اسم الملف: src/lib.rs
mod front_of_house; pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }
الشيفرة 21: تصريح الوحدة front_of_house التي سيتواجد محتواها في الملف src/front_of_house.rs
ننقل الشيفرة البرمجية التي كانت موجودة في الأقواس المعقوصة إلى ملف جديد يسمى src/front_of_house.rs كما هو موضح في الشيفرة 22. سيعلم المصرّف مكان هذا الملف لأنه سيصادف التصريح عن الوحدة front_of_house
في جذر الوحدة المُصرّفة.
اسم الملف: src/front_of_house.rs
pub mod hosting { pub fn add_to_waitlist() {} }
الشيفرة 22: التعريف داخل الوحدة front_house ضمن الملف src/front_of_house.rs
لاحظ أنك تستطيع تحميل ملف ما باستخدام تصريح mod
مرةً واحدةً فقط ضمن شجرة الوحدة، وحالما يعلم المصرف أن هذا الملف ينتمي إلى المشروع (بالإضافة إلى معرفته لمكان الملف ضمن شجرة الوحدة بفضل المكان الذي كتبت فيه تعليمة mod
)، ويجب أن تشير الملفات الأخرى إلى الشيفرة البرمجية الخاصة بالملف المُحمّل باستخدام مسار يشير إلى مكان التصريح عنه كما ناقشنا في الفقرات السابقة. بكلمات أخرى، لا تشبه mod
عملية تضمين "include"، الموجودة في بعض لغات البرمجة الأخرى.
الآن، نستخرج الوحدة hosting
إلى ملفها الخاص، إلا أن العملية مختلفة هنا بعض الشيء لأن hosting
هي وحدة ابن من الوحدة front_of_house
وليس وحدة ابن من الوحدة الجذر، ولذلك سنضع ملف الوحدة hosting
في مسار جديد يُسمّى بحسب آباء الوحدة في شجرة الوحدة، وفي هذه الحالة سيكون المسار هو src/front_of_house/.
لنقل الوحدة hosting
نعدّل الملف src/front_of_house.rs بحيث يحتوي على تصريح الوحدة hosting
فقط:
اسم الملف: src/front_of_house.rs
pub mod hosting;
نُنشئ بعد ذلك مسار src/front_of_house وملف hosting.rs ليحتوي على التعريف الخاص بالوحدة hosting
:
اسم الملف: src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
إذا وضعنا hosting.rs في المجلد src بدلًا من المجلد الحالي فإن المصرّف سيتوقع رؤية شيفرة hosting.rs في الوحدة hosting
المصرح عنها في جذر الوحدة المُصرّفة وليس المصرح عنها وحدة ابن للوحدة front_of_house
. تُملي قواعد المصرف بخصوص مسارات الملفات والوحدات أهمية تنظيمها بالنسبة لشجرة الوحدة الخاصة بالمشروع.
نقلنا الشيفرة البرمجية الخاصة بكل وحدة إلى ملف منفصل وبقيت شجرة الوحدة على حالها، وستعمل استدعاءات الدالة في eat_at_restaurant
دون أي تعديلات على الرغم من أن التعريفات موجودة في ملفات مختلفة، وتسمح لنا هذه الطريقة بتحريك الوحدات إلى ملفات جديدة بسهولة مع نموّ حجم مشروعك.
لاحظ أن تعليمة pub use crate::front_of_house::hosting
الموجودة في src/lib.rs لم تتغير، إذ لا تؤثّر use
على الملفات التي ستُصرَّف مثل جزء من الوحدة المُصرّفة. تعرّف الكلمة المفتاحية mod
الوحدات، إلا أن رست تنظر إلى الملف الذي يحمل الاسم ذاته الخاص بالوحدة للبحث عن الشيفرة البرمجية الخاصة بتلك الوحدة.
مسارات ملفات مختلفة
غطّينا بحلول هذه النقطة مسارات الملفات الشائعة والاصطلاحية في مصرّف رست، إلا أن رست تدعم أيضًا بعض التنسيقات القديمة لمسارات الملفات، فبالنسبة لوحدة تدعى front_of_house
مصرح عنها في جذر الوحدة المُصرّفة، ينظر المصرف للشيفرة البرمجية الخاصة بالوحدة في:
- المسار src/front_of_house.rs (وهو مسار تكلمنا عنه سابقًا).
- المسار src/front_of_house/mod.rs (مسار قديم إلا أنه مدعوم).
سينظر المصرف بالنسبة لوحدة جزئية من front_of_house
تدعى hosting
إلى اللشيفرة البرمجية الخاصة بالوحدة في:
- المسار src/front_of_house/hosting.rs (مسار ناقشناه سابقًا).
- المسار src/front_of_house/hosting/mod.rs (مسار قديم إلا أنه مدعوم).
إذا استخدمت كلا الأسلوبين للوحدة ذاتها، ستحصل على خطأ من المصرّف، إلا أن استخدام كلا النوعين في ذات المشروع لوحدات مختلفة مسموح، ولكنه قد يتسبب بالخلط لبعض الناس الذين يقرؤون الشيفرة البرمجية.
النقطة السلبية الأساسية للأسلوب الذي يستخدم ملفات بالاسم mod.rs هو انتهاء مشروعك غالبًا باحتوائه على كثير من الملفات تحمل الاسم mod.rs، وهذا قد يثير الإرباك عندما تحاول تعديل الشيفرة البرمجية ضمن محرر ما.
ترجمة -وبتصرف- لقسم من الفصل Managing Growing Projects with Packages, Crates, and Modules من كتاب The Rust Programming Language.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.