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

المسارات paths والنطاق الخاص بها في لغة رست Rust


Naser Dakhel

قد تكون عملية كتابة مسارات استدعاء الدوال في بعض الأحيان عملية غير مريحة ورتيبة، كان علينا في الشيفرة 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.

اقرأ أيضًا


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...