مسیرها برای اشاره به یک آیتم در درخت ماژول

برای نشان دادن به Rust که یک آیتم را در درخت ماژول کجا پیدا کند، از یک مسیر استفاده می‌کنیم، مشابه استفاده از مسیر هنگام پیمایش در یک فایل‌سیستم. برای فراخوانی یک تابع، باید مسیر آن را بدانیم.

یک مسیر می‌تواند به دو شکل باشد:

  • یک مسیر مطلق مسیری کامل است که از ریشه جعبه (crate) شروع می‌شود؛ برای کدی که از یک جعبه (crate) خارجی می‌آید، مسیر مطلق با نام جعبه (crate) شروع می‌شود، و برای کدی که از جعبه (crate) فعلی می‌آید، با کلمه کلیدی crate شروع می‌شود.
  • یک مسیر نسبی از ماژول فعلی شروع می‌شود و از self، super یا یک شناسه در ماژول فعلی استفاده می‌کند.

هر دو مسیر مطلق و نسبی با یک یا چند شناسه که با دو نقطه دوبل (::) جدا شده‌اند دنبال می‌شوند.

با بازگشت به لیستینگ 7-1، فرض کنید که می‌خواهیم تابع add_to_waitlist را فراخوانی کنیم. این کار مشابه پرسیدن این است: مسیر تابع add_to_waitlist چیست؟ لیستینگ 7-3 شامل لیستینگ 7-1 با حذف برخی از ماژول‌ها و توابع است.

ما دو روش برای فراخوانی تابع add_to_waitlist از یک تابع جدید، eat_at_restaurant، که در ریشه جعبه (crate) تعریف شده است، نشان خواهیم داد. این مسیرها درست هستند، اما یک مشکل دیگر وجود دارد که مانع کامپایل این مثال به شکل فعلی می‌شود. بعداً توضیح خواهیم داد که چرا.

تابع eat_at_restaurant بخشی از API عمومی جعبه (crate) کتابخانه‌ای ما است، بنابراین آن را با کلمه کلیدی pub علامت می‌زنیم. در بخش «آشکار کردن مسیرها با کلمه کلیدی pub»، به جزئیات بیشتری درباره pub خواهیم پرداخت.

Filename: src/lib.rs
mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}
Listing 7-3: فراخوانی تابع add_to_waitlist با استفاده از مسیرهای مطلق و نسبی

بار اولی که تابع add_to_waitlist را در eat_at_restaurant فراخوانی می‌کنیم، از یک مسیر مطلق استفاده می‌کنیم. تابع add_to_waitlist در همان جعبه (crate) تعریف شده است که eat_at_restaurant در آن قرار دارد، که به این معنی است که می‌توانیم از کلمه کلیدی crate برای شروع مسیر مطلق استفاده کنیم. سپس هر یک از ماژول‌های متوالی را شامل می‌کنیم تا به add_to_waitlist برسیم. می‌توانید یک فایل‌سیستم با ساختار مشابه تصور کنید: ما مسیر /front_of_house/hosting/add_to_waitlist را برای اجرای برنامه add_to_waitlist مشخص می‌کنیم؛ استفاده از نام crate برای شروع از ریشه جعبه (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 تغییر نمی‌کند، اما مسیر نسبی باید به‌روزرسانی شود. ترجیح ما به طور کلی این است که مسیرهای مطلق را مشخص کنیم زیرا احتمال بیشتری دارد که بخواهیم تعریف کد و فراخوانی آیتم‌ها را مستقل از یکدیگر جابجا کنیم.

بیایید سعی کنیم کد لیستینگ 7-3 را کامپایل کنیم و ببینیم چرا هنوز کامپایل نمی‌شود! خطاهایی که دریافت می‌کنیم در لیستینگ 7-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();
  |                            ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported
  |                            |
  |                            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();
   |                     ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported
   |                     |
   |                     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` (lib) due to 2 previous errors
Listing 7-4: خطاهای کامپایلر هنگام ساخت کد در لیستینگ 7-3

پیام‌های خطا می‌گویند که ماژول hosting خصوصی است. به عبارت دیگر، ما مسیرهای صحیح برای ماژول hosting و تابع add_to_waitlist داریم، اما Rust به ما اجازه نمی‌دهد از آن‌ها استفاده کنیم زیرا به بخش‌های خصوصی دسترسی ندارد. در Rust، تمام آیتم‌ها (توابع، متدها، ساختارها، enumها، ماژول‌ها و ثابت‌ها) به صورت پیش‌فرض برای ماژول‌های والد خصوصی هستند. اگر بخواهید آیتمی مانند یک تابع یا ساختار را خصوصی کنید، آن را در یک ماژول قرار می‌دهید.

آیتم‌های موجود در یک ماژول والد نمی‌توانند از آیتم‌های خصوصی درون ماژول‌های فرزند استفاده کنند، اما آیتم‌های درون ماژول‌های فرزند می‌توانند از آیتم‌های ماژول‌های اجداد خود استفاده کنند. این به این دلیل است که ماژول‌های فرزند جزئیات پیاده‌سازی خود را بسته‌بندی و پنهان می‌کنند، اما ماژول‌های فرزند می‌توانند زمینه‌ای که در آن تعریف شده‌اند را ببینند. برای ادامه مثال، قواعد حریم خصوصی را مانند دفتر پشتی یک رستوران تصور کنید: آنچه در آنجا می‌گذرد برای مشتریان رستوران خصوصی است، اما مدیران دفتر می‌توانند همه چیز را در رستوران ببینند و انجام دهند.

Rust تصمیم گرفته است که سیستم ماژول به این صورت کار کند تا پنهان کردن جزئیات پیاده‌سازی داخلی به صورت پیش‌فرض باشد. به این ترتیب، می‌دانید کدام بخش‌های کد داخلی را می‌توانید تغییر دهید بدون اینکه کد بیرونی را خراب کنید. با این حال، Rust به شما این امکان را می‌دهد که بخش‌های داخلی کد ماژول‌های فرزند را به ماژول‌های اجداد بیرونی با استفاده از کلمه کلیدی pub عمومی کنید.

آشکار کردن مسیرها با کلمه کلیدی pub

بیایید به خطای لیستینگ 7-4 برگردیم که به ما گفت ماژول hosting خصوصی است. ما می‌خواهیم تابع eat_at_restaurant در ماژول والد به تابع add_to_waitlist در ماژول فرزند دسترسی داشته باشد، بنابراین ماژول hosting را با کلمه کلیدی pub علامت می‌زنیم، همان‌طور که در لیستینگ 7-5 نشان داده شده است.

Filename: src/lib.rs
mod front_of_house {
    pub mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}
Listing 7-5: اعلان ماژول hosting به عنوان pub برای استفاده از آن در eat_at_restaurant

متأسفانه، کد در لیستینگ 7-5 همچنان به خطاهای کامپایلر منجر می‌شود، همان‌طور که در لیستینگ 7-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` (lib) due to 2 previous errors
Listing 7-6: خطاهای کامپایلر هنگام ساخت کد در لیستینگ 7-5

چه اتفاقی افتاد؟ اضافه کردن کلمه کلیدی pub در جلوی mod hosting ماژول را عمومی می‌کند. با این تغییر، اگر به front_of_house دسترسی داشته باشیم، می‌توانیم به hosting نیز دسترسی داشته باشیم. اما محتویات hosting همچنان خصوصی است؛ عمومی کردن ماژول به معنای عمومی کردن محتوای آن نیست. کلمه کلیدی pub روی یک ماژول فقط به کدهای موجود در ماژول‌های اجداد اجازه می‌دهد به آن ارجاع دهند، نه اینکه به کد داخلی آن دسترسی داشته باشند. از آنجایی که ماژول‌ها به عنوان ظرف عمل می‌کنند، تنها عمومی کردن ماژول کافی نیست؛ باید فراتر رفته و یک یا چند مورد از آیتم‌های درون ماژول را نیز عمومی کنیم.

خطاهای موجود در لیستینگ 7-6 نشان می‌دهند که تابع add_to_waitlist خصوصی است. قواعد حریم خصوصی برای ساختارها، enumها، توابع، متدها و همچنین ماژول‌ها اعمال می‌شوند.

بیایید تابع add_to_waitlist را نیز با اضافه کردن کلمه کلیدی pub قبل از تعریف آن عمومی کنیم، همان‌طور که در لیستینگ 7-7 نشان داده شده است.

Filename: src/lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}
Listing 7-7: اضافه کردن کلمه کلیدی pub به mod hosting و fn add_to_waitlist به ما اجازه می‌دهد تابع را از eat_at_restaurant فراخوانی کنیم

حالا کد کامپایل می‌شود! برای اینکه ببینیم چرا اضافه کردن کلمه کلیدی pub به ما اجازه می‌دهد از این مسیرها در eat_at_restaurant استفاده کنیم، بیایید به مسیرهای مطلق و نسبی نگاه کنیم.

در مسیر مطلق، با crate، ریشه درخت ماژول جعبه (crate) خود شروع می‌کنیم. ماژول front_of_house در ریشه جعبه (crate) تعریف شده است. اگرچه front_of_house عمومی نیست، از آنجا که تابع eat_at_restaurant در همان ماژول به عنوان front_of_house تعریف شده است (یعنی eat_at_restaurant و front_of_house هم‌سطح هستند)، می‌توانیم از eat_at_restaurant به front_of_house ارجاع دهیم. بعد، ماژول hosting که با pub علامت‌گذاری شده است قرار دارد. ما می‌توانیم به ماژول والد hosting دسترسی داشته باشیم، بنابراین می‌توانیم به hosting دسترسی داشته باشیم. در نهایت، تابع add_to_waitlist با pub علامت‌گذاری شده است و می‌توانیم به ماژول والد آن دسترسی داشته باشیم، بنابراین این فراخوانی تابع کار می‌کند!

در مسیر نسبی، منطق همان مسیر مطلق است با این تفاوت که مرحله اول متفاوت است: به جای شروع از ریشه جعبه (crate)، مسیر از front_of_house شروع می‌شود. ماژول front_of_house در همان ماژولی که eat_at_restaurant تعریف شده است قرار دارد، بنابراین مسیر نسبی که از ماژولی که eat_at_restaurant در آن تعریف شده است شروع می‌شود کار می‌کند. سپس، از آنجا که hosting و add_to_waitlist با pub علامت‌گذاری شده‌اند، بقیه مسیر کار می‌کند و این فراخوانی تابع معتبر است!

اگر قصد دارید جعبه (crate) کتابخانه خود را به اشتراک بگذارید تا پروژه‌های دیگر بتوانند از کد شما استفاده کنند، API عمومی شما قرارداد شما با کاربران جعبه (crate) است که تعیین می‌کند چگونه می‌توانند با کد شما تعامل داشته باشند. نکات زیادی در مورد مدیریت تغییرات API عمومی شما وجود دارد که به افراد کمک می‌کند به جعبه (crate) شما وابسته باشند. این ملاحظات خارج از دامنه این کتاب هستند؛ اگر به این موضوع علاقه‌مند هستید، به راهنمای API Rust مراجعه کنید.

بهترین شیوه‌ها برای بسته‌هایی که یک جعبه (crate) باینری و یک جعبه (crate) کتابخانه‌ای دارند

ما اشاره کردیم که یک بسته می‌تواند هم یک ریشه جعبه (crate) باینری در src/main.rs و هم یک ریشه جعبه (crate) کتابخانه‌ای در src/lib.rs داشته باشد، و هر دو جعبه (crate) به صورت پیش‌فرض نام بسته را خواهند داشت. معمولاً بسته‌هایی که این الگو را دنبال می‌کنند فقط به اندازه کافی کد در جعبه (crate) باینری دارند تا یک فایل اجرایی ایجاد کنند که کدی درون جعبه (crate) کتابخانه‌ای را فراخوانی کند. این کار به پروژه‌های دیگر اجازه می‌دهد از بیشتر عملکردهایی که بسته ارائه می‌دهد بهره‌مند شوند، زیرا کد جعبه (crate) کتابخانه‌ای می‌تواند به اشتراک گذاشته شود.

درخت ماژول باید در src/lib.rs تعریف شود. سپس، هر آیتم عمومی را می‌توان در جعبه (crate) باینری با شروع مسیرها با نام بسته استفاده کرد. جعبه (crate) باینری به یک کاربر از جعبه (crate) کتابخانه‌ای تبدیل می‌شود، درست مثل اینکه یک جعبه (crate) کاملاً خارجی از جعبه (crate) کتابخانه‌ای استفاده می‌کند: تنها می‌تواند از API عمومی استفاده کند. این کار به شما کمک می‌کند یک API خوب طراحی کنید؛ نه تنها نویسنده آن هستید، بلکه یک کاربر نیز هستید!

در فصل ۱۲، ما این شیوه سازمان‌دهی را با یک برنامه خط فرمان که هم یک جعبه (crate) باینری و هم یک جعبه (crate) کتابخانه‌ای دارد نشان خواهیم داد.

شروع مسیرهای نسبی با super

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

کد موجود در لیستینگ 7-8 را در نظر بگیرید که موقعیتی را مدل‌سازی می‌کند که در آن یک آشپز سفارش نادرست را اصلاح کرده و شخصاً آن را به مشتری می‌آورد. تابع fix_incorrect_order که در ماژول back_of_house تعریف شده است، تابع deliver_order را که در ماژول والد تعریف شده است، فراخوانی می‌کند و مسیر deliver_order را با شروع از super مشخص می‌کند.

Filename: src/lib.rs
fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}
Listing 7-8: فراخوانی یک تابع با استفاده از یک مسیر نسبی که با super شروع می‌شود

تابع fix_incorrect_order در ماژول back_of_house است، بنابراین می‌توانیم از super برای رفتن به ماژول والد back_of_house استفاده کنیم، که در این مورد crate، یعنی ریشه است. از آنجا به دنبال deliver_order می‌گردیم و آن را پیدا می‌کنیم. موفقیت! ما فکر می‌کنیم که ماژول back_of_house و تابع deliver_order احتمالاً در همان رابطه با یکدیگر باقی می‌مانند و اگر بخواهیم درخت ماژول جعبه (crate) را سازماندهی مجدد کنیم، با هم جابجا می‌شوند. بنابراین، از super استفاده کردیم تا در آینده، اگر این کد به ماژول دیگری منتقل شد، تغییرات کمتری در کد لازم باشد.

عمومی کردن ساختارها و enumها

ما همچنین می‌توانیم از pub برای مشخص کردن ساختارها و enumها به عنوان عمومی استفاده کنیم، اما چند جزئیات اضافی در مورد استفاده از pub با ساختارها و enumها وجود دارد. اگر از pub قبل از تعریف یک ساختار استفاده کنیم، ساختار عمومی می‌شود، اما فیلدهای ساختار همچنان خصوصی خواهند بود. ما می‌توانیم هر فیلد را به صورت موردی عمومی یا خصوصی کنیم. در لیستینگ 7-9، یک ساختار عمومی به نام back_of_house::Breakfast تعریف کرده‌ایم که یک فیلد عمومی به نام toast دارد اما فیلد seasonal_fruit خصوصی است. این مدل‌سازی حالتی است که در آن مشتری می‌تواند نوع نان همراه با وعده غذایی را انتخاب کند، اما سرآشپز تصمیم می‌گیرد که کدام میوه همراه وعده غذایی باشد بر اساس آنچه در فصل و موجودی است. میوه‌های موجود به سرعت تغییر می‌کنند، بنابراین مشتریان نمی‌توانند میوه را انتخاب کنند یا حتی ببینند که چه میوه‌ای دریافت خواهند کرد.

Filename: 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() {
    // Order a breakfast in the summer with Rye toast
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // Change our mind about what bread we'd like
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);

    // The next line won't compile if we uncomment it; we're not allowed
    // to see or modify the seasonal fruit that comes with the meal
    // meal.seasonal_fruit = String::from("blueberries");
}
Listing 7-9: یک ساختار با برخی فیلدهای عمومی و برخی خصوصی

از آنجا که فیلد toast در ساختار back_of_house::Breakfast عمومی است، می‌توانیم در eat_at_restaurant به این فیلد با استفاده از نقطه‌گذاری مقدار بدهیم یا مقدار آن را بخوانیم. توجه کنید که نمی‌توانیم از فیلد seasonal_fruit در eat_at_restaurant استفاده کنیم، زیرا seasonal_fruit خصوصی است. خطی که مقدار فیلد seasonal_fruit را تغییر می‌دهد را لغو کامنت کنید تا ببینید چه خطایی دریافت می‌کنید!

همچنین توجه کنید که چون back_of_house::Breakfast یک فیلد خصوصی دارد، ساختار باید یک تابع وابسته عمومی ارائه دهد که یک نمونه از Breakfast بسازد (ما آن را اینجا summer نامیده‌ایم). اگر Breakfast چنین تابعی نداشت، نمی‌توانستیم یک نمونه از Breakfast را در eat_at_restaurant ایجاد کنیم، زیرا نمی‌توانستیم مقدار فیلد خصوصی seasonal_fruit را در eat_at_restaurant تنظیم کنیم.

در مقابل، اگر یک enum را عمومی کنیم، تمام متغیرهای آن نیز عمومی می‌شوند. ما فقط به pub قبل از کلمه کلیدی enum نیاز داریم، همان‌طور که در لیستینگ 7-10 نشان داده شده است.

Filename: 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;
}
Listing 7-10: عمومی کردن یک enum تمام متغیرهای آن را عمومی می‌کند

از آنجایی که enum Appetizer را عمومی کردیم، می‌توانیم از متغیرهای Soup و Salad در eat_at_restaurant استفاده کنیم.

Enums خیلی مفید نیستند مگر اینکه متغیرهای آن‌ها عمومی باشند؛ اضافه کردن pub به تمام متغیرهای enum در هر مورد کار خسته‌کننده‌ای خواهد بود، بنابراین به طور پیش‌فرض متغیرهای enum عمومی هستند. ساختارها اغلب بدون عمومی بودن فیلدهایشان مفید هستند، بنابراین فیلدهای ساختار از قانون کلی پیروی می‌کنند که همه چیز به صورت پیش‌فرض خصوصی است مگر اینکه با pub مشخص شود.

یک وضعیت دیگر مرتبط با pub وجود دارد که هنوز آن را پوشش نداده‌ایم، و آن آخرین ویژگی سیستم ماژول ما است: کلمه کلیدی use. ابتدا use را به تنهایی بررسی خواهیم کرد، و سپس نشان خواهیم داد چگونه pub و use را ترکیب کنیم.