تعریف ماژول‌ها برای کنترل محدوده و حریم خصوصی

در این بخش، ما درباره ماژول‌ها و سایر بخش‌های سیستم ماژول صحبت خواهیم کرد، یعنی مسیرها که به شما امکان می‌دهند آیتم‌ها را نام‌گذاری کنید؛ کلمه کلیدی use که مسیر را به محدوده وارد می‌کند؛ و کلمه کلیدی pub برای عمومی کردن آیتم‌ها. همچنین درباره کلمه کلیدی as، بسته‌های خارجی، و عملگر glob صحبت خواهیم کرد.

خلاصه‌ای از ماژول‌ها

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

  • شروع از ریشه جعبه (crate): هنگام کامپایل یک جعبه (crate)، کامپایلر ابتدا در فایل ریشه جعبه (crate) (معمولاً src/lib.rs برای یک جعبه (crate) کتابخانه‌ای یا src/main.rs برای یک جعبه (crate) باینری) به دنبال کد برای کامپایل می‌گردد.
  • تعریف ماژول‌ها: در فایل ریشه جعبه (crate)، می‌توانید ماژول‌های جدید تعریف کنید؛ مثلاً می‌توانید یک ماژول “garden” با mod garden; تعریف کنید. کامپایلر کد ماژول را در مکان‌های زیر جستجو می‌کند:
    • به صورت درون‌خطی، داخل براکت‌های موج‌دار که به جای علامت نقطه‌ویرگول بعد از mod garden قرار می‌گیرند.
    • در فایل src/garden.rs
    • در فایل src/garden/mod.rs
  • تعریف زیرماژول‌ها: در هر فایلی به جز فایل ریشه جعبه (crate)، می‌توانید زیرماژول‌ها تعریف کنید. برای مثال، ممکن است mod vegetables; را در فایل src/garden.rs تعریف کنید. کامپایلر کد زیرماژول را در دایرکتوری‌ای که به نام ماژول والد است، در مکان‌های زیر جستجو می‌کند:
    • به صورت درون‌خطی، مستقیماً بعد از mod vegetables، داخل براکت‌های موج‌دار به جای نقطه‌ویرگول
    • در فایل src/garden/vegetables.rs
    • در فایل src/garden/vegetables/mod.rs
  • مسیرها به کد در ماژول‌ها: وقتی یک ماژول بخشی از جعبه (crate) شما باشد، می‌توانید از هر جای دیگر در همان جعبه (crate) (تا زمانی که قواعد حریم خصوصی اجازه دهند) با استفاده از مسیر به کد آن ارجاع دهید. برای مثال، یک نوع Asparagus در ماژول vegetables در garden به این صورت پیدا می‌شود: crate::garden::vegetables::Asparagus.
  • خصوصی در مقابل عمومی: کد درون یک ماژول به صورت پیش‌فرض برای ماژول‌های والد خصوصی است. برای عمومی کردن یک ماژول، آن را با pub mod به جای mod تعریف کنید. برای عمومی کردن آیتم‌های داخل یک ماژول عمومی، از pub قبل از اعلان آن‌ها استفاده کنید.
  • کلمه کلیدی use: در یک محدوده، کلمه کلیدی use میانبری به آیتم‌ها ایجاد می‌کند تا تکرار مسیرهای طولانی کاهش یابد. در هر محدوده‌ای که می‌تواند به crate::garden::vegetables::Asparagus ارجاع دهد، می‌توانید یک میانبر با use crate::garden::vegetables::Asparagus; ایجاد کنید و از آن به بعد فقط کافی است Asparagus را در آن محدوده استفاده کنید.

اینجا، ما یک جعبه (crate) باینری به نام backyard ایجاد می‌کنیم که این قواعد را نشان می‌دهد. دایرکتوری جعبه (crate) که آن هم backyard نامیده می‌شود شامل این فایل‌ها و دایرکتوری‌ها است:

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
    │   └── vegetables.rs
    ├── garden.rs
    └── main.rs

فایل ریشه جعبه (crate) در اینجا src/main.rs است و حاوی موارد زیر است:

Filename: src/main.rs
use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
    let plant = Asparagus {};
    println!("I'm growing {plant:?}!");
}

خط pub mod garden; به کامپایلر می‌گوید که کدی را که در src/garden.rs پیدا می‌کند وارد کند، که شامل موارد زیر است:

Filename: src/garden.rs
pub mod vegetables;

اینجا، pub mod vegetables; به این معنا است که کد موجود در src/garden/vegetables.rs نیز وارد می‌شود. آن کد به صورت زیر است:

#[derive(Debug)]
pub struct Asparagus {}

حالا بیایید به جزئیات این قواعد بپردازیم و آن‌ها را در عمل نشان دهیم!

گروه‌بندی کدهای مرتبط در ماژول‌ها

ماژول‌ها به ما امکان می‌دهند کد را در یک جعبه (crate) برای خوانایی و بازاستفاده آسان سازماندهی کنیم. ماژول‌ها همچنین به ما امکان کنترل حریم خصوصی آیتم‌ها را می‌دهند زیرا کد درون یک ماژول به صورت پیش‌فرض خصوصی است. آیتم‌های خصوصی جزئیات پیاده‌سازی داخلی هستند که برای استفاده خارجی در دسترس نیستند. ما می‌توانیم انتخاب کنیم که ماژول‌ها و آیتم‌های درون آن‌ها عمومی باشند، که این موارد را برای استفاده خارجی آشکار می‌کند.

برای مثال، بیایید یک جعبه (crate) کتابخانه‌ای بنویسیم که عملکرد یک رستوران را ارائه دهد. امضای توابع را تعریف می‌کنیم اما بدنه آن‌ها را خالی می‌گذاریم تا بیشتر بر سازماندهی کد تمرکز کنیم تا پیاده‌سازی عملکرد یک رستوران.

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

برای ساختاردهی جعبه (crate) خود به این روش، می‌توانیم عملکردها را در ماژول‌های تو در تو سازماندهی کنیم. یک کتابخانه جدید به نام restaurant با اجرای دستور cargo new restaurant --lib ایجاد کنید. سپس کد لیستینگ 7-1 را در src/lib.rs وارد کنید تا برخی ماژول‌ها و امضای توابع تعریف شود. این کد بخش جلوی خانه را تعریف می‌کند.

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

        fn seat_at_table() {}
    }

    mod serving {
        fn take_order() {}

        fn serve_order() {}

        fn take_payment() {}
    }
}
Listing 7-1: یک ماژول front_of_house که شامل ماژول‌های دیگر است که سپس شامل توابع می‌شوند

ما یک ماژول با کلمه کلیدی mod و سپس نام ماژول تعریف می‌کنیم (در این مورد، front_of_house). بدنه ماژول سپس داخل براکت‌های موج‌دار قرار می‌گیرد. داخل ماژول‌ها، می‌توانیم ماژول‌های دیگری قرار دهیم، همان‌طور که در اینجا با ماژول‌های hosting و serving انجام داده‌ایم. ماژول‌ها همچنین می‌توانند تعاریف آیتم‌های دیگر را نگه دارند، مانند ساختارها، enumها، ثابت‌ها، traits و—همان‌طور که در لیستینگ 7-1 دیده می‌شود—توابع.

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

درخت ماژول

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

لیستینگ 7-2 درخت ماژول را برای ساختار موجود در لیستینگ 7-1 نشان می‌دهد.

crate
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment
Listing 7-2: درخت ماژول برای کد موجود در لیستینگ 7-1

این درخت نشان می‌دهد که برخی از ماژول‌ها در داخل ماژول‌های دیگر قرار دارند؛ برای مثال، hosting در داخل front_of_house قرار دارد. درخت همچنین نشان می‌دهد که برخی از ماژول‌ها هم‌سطح هستند، به این معنی که در همان ماژول تعریف شده‌اند؛ hosting و serving هم‌سطح هستند و درون front_of_house تعریف شده‌اند. اگر ماژول A درون ماژول B قرار گیرد، می‌گوییم ماژول A فرزند ماژول B است و ماژول B والد ماژول A است. توجه کنید که کل درخت ماژول در زیر ماژول ضمنی به نام crate ریشه دارد.

درخت ماژول ممکن است شما را به یاد درخت دایرکتوری‌های فایل‌سیستم کامپیوتر بیندازد؛ این مقایسه بسیار مناسبی است! درست همان‌طور که دایرکتوری‌ها در فایل‌سیستم کد را سازماندهی می‌کنند، شما می‌توانید از ماژول‌ها برای سازماندهی کد خود استفاده کنید. و درست مانند فایل‌ها در یک دایرکتوری، ما نیاز به روشی برای پیدا کردن ماژول‌ها داریم.