مقدمه

در درس قبل دیدیم که ماژول‌ها به ما اجازه می‌دهند تا کد خود را در یک ساختار درختی سازماندهی کنیم. برای نام بردن و دسترسی به آیتم‌ها (مانند توابع، structها یا حتی ماژول‌های دیگر) در این درخت، از «مسیر» یا Path استفاده می‌کنیم. مسیر در Rust مشابه مسیر در یک فایل سیستم عمل می‌کند و به ما کمک می‌کند تا به هر آیتمی در crate خود ارجاع دهیم. درک کامل نحوه کار با مسیرها برای نوشتن کدهای ماژولار و تمیز ضروری است.

مسیرهای مطلق در مقابل مسیرهای نسبی

همانطور که در درس قبل اشاره شد، دو نوع مسیر برای ارجاع به آیتم‌ها وجود دارد:

  • مسیر مطلق (Absolute Path): این مسیر همیشه از ریشه crate شروع می‌شود. برای این کار از کلمه کلیدی crate در ابتدای مسیر استفاده می‌کنیم. این روش بسیار واضح است زیرا به محل فعلی کد شما بستگی ندارد.
  • مسیر نسبی (Relative Path): این مسیر از ماژول فعلی شروع می‌شود و از کلمات کلیدی مانند self (برای ارجاع به خود ماژول فعلی) و super (برای ارجاع به ماژول والد) استفاده می‌کند. این روش برای ارجاع به آیتم‌های نزدیک در درخت ماژول مفید است.

استفاده از super برای پیمایش به بالا

کلمه کلیدی super به ما اجازه می‌دهد تا از ماژول فعلی یک سطح به بالا، یعنی به ماژول والد، برویم. این قابلیت زمانی مفید است که بخواهیم از داخل یک ماژول به یک آیتم در ماژول خواهر (sibling) آن دسترسی پیدا کنیم.

Copy Icon src/lib.rs
mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String, // This field is private
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

mod serving {
    fn take_order() {
        // We go up to the crate root's sibling `back_of_house` module
        // This path feels a bit weird.
        super::back_of_house::Breakfast::summer("Rye");
    }
}

در این مثال، از داخل تابع take_order در ماژول serving، با استفاده از super به ماژول ریشه رفته و سپس وارد ماژول back_of_house می‌شویم تا به تابع summer دسترسی پیدا کنیم.

ساخت رابط عمومی (Public API) با pub use

وقتی یک کتابخانه می‌نویسیم، ممکن است ساختار داخلی ماژول‌های ما برای سازماندهی کد، بسیار تودرتو و پیچیده باشد. ما نمی‌خواهیم کاربر نهایی کتابخانه ما مجبور باشد برای استفاده از یک تابع، مسیرهای طولانی مانند my_library::some::deeply::nested::module::useful_function() را تایپ کند. این کار هم سخت است و هم جزئیات پیاده‌سازی داخلی ما را فاش می‌کند.

برای حل این مشکل، از ترکیب pub و use استفاده می‌کنیم. دستور pub use یک آیتم را به حوزه فعلی وارد می‌کند (مانند use) و همزمان، آن میانبر را به صورت عمومی (public) در دسترس قرار می‌دهد. به این عمل «باز-خروجی گرفتن» یا re-exporting می‌گویند. این الگو به ما اجازه می‌دهد تا یک رابط عمومی (API) تمیز و منطقی برای کتابخانه خود بسازیم، در حالی که ساختار داخلی آن می‌تواند هر چقدر که لازم است پیچیده باشد.

Copy Icon src/lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

// Re-export the hosting module at the top level of our crate.
pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    // Now users of our library can call it with a much shorter path.
    // Instead of: my_library::front_of_house::hosting::add_to_waitlist()
    // They can use: my_library::hosting::add_to_waitlist()
    hosting::add_to_waitlist();
}

در این کد، با وجود اینکه ماژول hosting در داخل front_of_house قرار دارد، ما با استفاده از pub use یک میانبر عمومی به آن در سطح اصلی crate خود ایجاد کرده‌ایم. این کار باعث می‌شود که API کتابخانه ما برای کاربران نهایی بسیار ساده‌تر و قابل فهم‌تر باشد.

در این درس با جزئیات کار با مسیرها و نقش آنها در پیمایش درخت ماژول آشنا شدیم. دیدیم که چگونه می‌توان با مسیرهای مطلق و نسبی (با استفاده از super) به آیتم‌ها دسترسی پیدا کرد و چگونه با الگوی قدرتمند pub use، یک رابط عمومی تمیز برای کتابخانه‌های خود طراحی کنیم. تاکنون تمام ماژول‌های خود را در یک فایل واحد تعریف کرده‌ایم. با بزرگتر شدن ماژول‌ها، نیاز داریم تا آنها را در فایل‌های مجزایی قرار دهیم. در درس بعدی، به بررسی نحوه «جداسازی ماژول‌ها در فایل‌های مختلف» خواهیم پرداخت.