سازماندهی پروژه‌های بزرگ

به فصل «مدیریت پروژه با پکیج‌ها و ماژول‌ها» خوش آمدید. با افزایش حجم و پیچیدگی یک پروژه، سازماندهی کد به یکی از مهم‌ترین چالش‌ها تبدیل می‌شود. نوشتن تمام کدها در یک فایل واحد، به سرعت غیرقابل مدیریت می‌شود. زبان Rust یک سیستم قدرتمند برای سازماندهی کد ارائه می‌دهد که به شما اجازه می‌دهد کد خود را به بخش‌های منطقی و قابل استفاده مجدد تقسیم کنید. اساس این سیستم بر دو مفهوم کلیدی استوار است: Crate و Package. درک تفاوت و ارتباط این دو مفهوم برای مدیریت پروژه‌های بزرگ در Rust ضروری است.

Crate چیست؟

یک Crate کوچکترین واحد کامپایل در زبان Rust است. وقتی شما کدی را با کامپایلر Rust یعنی rustc کامپایل می‌کنید، ورودی این کامپایلر یک یا چند Crate است. کد داخل یک Crate به صورت یک واحد یکپارچه با هم کامپایل می‌شود.

Crateها به دو شکل اصلی وجود دارند:

  • Crate باینری (Binary Crate): این نوع Crate می‌تواند به یک فایل اجرایی مستقل کامپایل شود. یک Crate باینری باید حتماً یک تابع main داشته باشد که نقطه شروع اجرای برنامه است. طبق قرارداد، فایل src/main.rs «ریشه» (crate root) یک Crate باینری در یک پکیج است.
  • Crate کتابخانه‌ای (Library Crate): این نوع Crate به خودی خود قابل اجرا نیست و تابع main ندارد. هدف آن ارائه مجموعه‌ای از توابع، نوع‌ها و منطق است که توسط Crateهای دیگر (چه باینری و چه کتابخانه‌ای) مورد استفاده قرار گیرد. طبق قرارداد، فایل src/lib.rs ریشه یک Crate کتابخانه‌ای است.

پکیج چیست؟

یک Package یک یا چند Crate را در کنار هم بسته‌بندی می‌کند تا یک مجموعه از قابلیت‌ها را ارائه دهد. یک پکیج توسط فایل مانیفست Cargo.toml تعریف می‌شود. این فایل حاوی اطلاعاتی مانند نام پکیج، نسخه، نویسنده و وابستگی‌های آن به پکیج‌های دیگر است.

Cargo، ابزار مدیریت پروژه و بیلد در Rust، در واقع با پکیج‌ها کار می‌کند. وقتی شما یک دستور cargo build را اجرا می‌کنید، Cargo فایل Cargo.toml را خوانده و Crateهای داخل آن پکیج را کامپایل می‌کند. قوانین مربوط به ساختار یک پکیج به شرح زیر است:

  • یک پکیج می‌تواند حداکثر یک Crate کتابخانه‌ای داشته باشد.
  • یک پکیج می‌تواند هر تعداد Crate باینری داشته باشد.
  • یک پکیج باید حداقل یک Crate (از هر نوعی) داشته باشد.

یک پکیج در عمل

بیایید این مفاهیم را با یک مثال عملی بررسی کنیم. وقتی شما با دستور cargo new my_project یک پروژه جدید می‌سازید، Cargo در واقع یک پکیج جدید برای شما ایجاد می‌کند.

$ cargo new my_project
$ cd my_project
                    

ساختار این پکیج به صورت پیش‌فرض شامل فایل Cargo.toml و یک پوشه src است که داخل آن فایل main.rs قرار دارد. این یعنی Cargo یک پکیج ساخته که حاوی یک Crate باینری است و ریشه آن فایل src/main.rs می‌باشد.

حالا بیایید یک Crate کتابخانه‌ای به همین پکیج اضافه کنیم. برای این کار، کافیست یک فایل به نام lib.rs در پوشه src بسازیم.

Copy Icon src/lib.rs
// This function is part of the library crate and can be used by others.
// The `pub` keyword makes it public and accessible from outside.
pub fn add_one(x: i32) -> i32 {
    x + 1
}

اکنون پکیج ما هم یک Crate باینری و هم یک Crate کتابخانه‌ای دارد. ما می‌توانیم از کدهای Crate کتابخانه‌ای خود در Crate باینری استفاده کنیم. برای این کار، باید از نام پکیج خود (که در Cargo.toml تعریف شده) برای ارجاع به آن استفاده کنیم.

Copy Icon src/main.rs
// Use the `add_one` function from our own library crate.
// The library crate's name is the same as the package name by default.
use my_project::add_one;

fn main() {
    let num = 10;
    let answer = add_one(num);
    println!("Hello, world! {} plus one is {}!", num, answer);
}

وقتی cargo run را اجرا می‌کنیم، Cargo ابتدا Crate کتابخانه‌ای (src/lib.rs) را کامپایل می‌کند و سپس Crate باینری (src/main.rs) را کامپایل می‌کند و در حین این کار، به آن اجازه می‌دهد تا از توابع export شده (با کلمه کلیدی pub) در Crate کتابخانه‌ای استفاده کند.

در این درس با مفاهیم بنیادی پکیج و Crate به عنوان واحدهای اصلی سازماندهی و کامپایل کد در Rust آشنا شدیم. یک پکیج می‌تواند شامل یک یا چند Crate باشد که به ما اجازه می‌دهد منطق قابل استفاده مجدد را در یک Crate کتابخانه‌ای قرار داده و برنامه اصلی را در یک Crate باینری پیاده‌سازی کنیم. اما درون یک Crate، چگونه می‌توانیم کد را به بخش‌های کوچک‌تر و مدیریت‌پذیرتر تقسیم کنیم؟ در درس بعدی، به «نقش ماژول‌ها» خواهیم پرداخت و یاد می‌گیریم که چگونه با استفاده از سیستم ماژول، کد خود را سازماندهی کرده و کپسوله‌سازی را پیاده‌سازی کنیم.