مقدمه

ما در طول این دوره بارها از ماکروها استفاده کرده‌ایم، مانند println! و vec!. «ماکرو» به مجموعه‌ای از قابلیت‌ها در Rust اشاره دارد که به ما اجازه می‌دهند تا کدی بنویسیم که کد دیگری را تولید می‌کند. به این فرآیند «متاپروگرمینگ» (metaprogramming) می‌گویند. این قابلیت به ما کمک می‌کند تا از تکرار کد جلوگیری کرده و Domain-Specific Language یا DSLهای جدیدی را در خود زبان Rust ایجاد کنیم.

یک تفاوت کلیدی بین توابع و ماکروها این است که ماکروها قبل از اینکه کامپایلر کد را از نظر معنایی تحلیل کند، گسترش می‌یابند. این یعنی یک ماکرو می‌تواند تعداد متغیری از آرگومان‌ها را بپذیرد (مانند println!) که با توابع عادی ممکن نیست.

ماکروهای اعلانی (Declarative Macros)

رایج‌ترین نوع ماکرو در Rust، «ماکروی اعلانی» است. این ماکروها با استفاده از ساختار macro_rules! تعریف می‌شوند و بر اساس تطبیق الگو (pattern matching) عمل می‌کنند. آنها شبیه به یک عبارت match هستند، اما به جای مقادیر، روی ساختار کد Rust عمل می‌کنند.

Copy Icon src/main.rs
#[macro_export]
macro_rules! my_vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

fn main() {
    let v = my_vec![1, 2, 3];
    println!("{:?}", v);
}

در این مثال، ما یک نسخه ساده‌شده از ماکروی vec! را به نام my_vec تعریف کرده‌ایم. بخش ( $( $x:expr ),* ) یک الگو است که با صفر یا چند عبارت (expr) که با کاما از هم جدا شده‌اند، مطابقت پیدا می‌کند. بخش بدنه ماکرو، کدی را که باید جایگزین شود، تعریف می‌کند. در اینجا، یک وکتور جدید ساخته و تمام عبارت‌های ورودی را در آن push می‌کند.

ماکروهای رویه‌ای (Procedural Macros)

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

سه نوع ماکروی رویه‌ای وجود دارد:

  • ماکروهای derive سفارشی: به شما اجازه می‌دهند تا traitها را به صورت خودکار برای یک struct یا enum با افزودن اتریبیوت #[derive] پیاده‌سازی کنید. ما قبلاً از این قابلیت برای #[derive(Debug)] استفاده کرده‌ایم.
  • ماکروهای شبیه به اتریبیوت (Attribute-like macros): به شما اجازه می‌دهند تا اتریبیوت‌های سفارشی بسازید که می‌توانند به هر آیتمی متصل شوند. برای مثال، در فریم‌ورک‌های وب مانند Actix Web، از آنها برای تعریف مسیرها استفاده می‌شود: #[get("/")].
  • ماکروهای شبیه به تابع (Function-like macros): شبیه به ماکروهای macro_rules! هستند اما انعطاف‌پذیری بسیار بیشتری در پردازش توکن‌ها دارند.

خلاصه

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

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