পাঠ ২০.৫

Macro

Macros

Macro মানে — code লেখার জন্য code। metaprogramming। Rust-এ macro দু'ধরনের:

  1. Declarative macromacro_rules!
  2. Procedural macro — তিন রকম: custom derive, attribute-like, function-like।

Macro বনাম function

Macro-র সুবিধা:

  • Variable number of argument — println!("hello") এবং println!("hello ", name) দু'টোই কাজ করে।
  • Compile-এর আগে expand হয় — তাই trait implement-ও করতে পারে, যেটা function পারে না।
  • Pattern-ভিত্তিক code generate।

সীমাবদ্ধতা:

  • Definition function-এর চেয়ে জটিল।
  • পড়া, বুঝা, maintain করা কঠিন।
  • Function-এর মতো যেখান থেকে সেখান থেকে call হয় না — call করার আগে scope-এ আসতে হবে।

Declarative macro — macro_rules!

এদের বলে "macros by example" — pattern match করে replacement code দেয়। সরলীকৃত vec!:

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

Pattern parts:

  • #[macro_export] — crate-এর বাইরে use-এর জন্য available।
  • $ — macro variable শুরু।
  • $x:expr — যেকোনো Rust expression match করো, তাকে $x নাম দাও।
  • $( ... ),* — comma-separated, zero বা more repetition।

vec![1, 2, 3] এই code-এ expand হয়:

{
    let mut temp_vec = Vec::new();
    temp_vec.push(1);
    temp_vec.push(2);
    temp_vec.push(3);
    temp_vec
}

Replacement-এর $()* প্রতিটা matched pattern-এর জন্য একবার generate করে।

Procedural macro

Procedural macro code input হিসেবে নেয়, প্রক্রিয়া করে, এবং code output দেয় — pattern matching না, Rust code-এ processing।

use proc_macro::TokenStream;

#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}

মূল ব্যাপার:

  • TokenStream — token-এর sequence।
  • নিজের আলাদা crate লাগে; Cargo.toml-এ proc-macro = true
  • সাহায্যকারী crate — syn (parse), quote (generate), proc_macro (compiler API)।

Custom derive macro

User struct/enum-এ #[derive(...)] দিয়ে trait auto-implement করানো। উদাহরণ — HelloMacro trait।

Trait definition crate (hello_macro):

pub trait HelloMacro {
    fn hello_macro();
}

Procedural macro crate (hello_macro_derive) — Cargo.toml:

[lib]
proc-macro = true

[dependencies]
syn = "2.0"
quote = "1.0"

Macro definition:

use proc_macro::TokenStream;
use quote::quote;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_hello_macro(&ast)
}

fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let generated = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}!", stringify!(#name));
            }
        }
    };
    generated.into()
}

লক্ষ করো:

  • syn::parse TokenStream-কে DeriveInput-এ পার্স করে।
  • quote! — template; #name substitution।
  • stringify! — compile-time-এ identifier স্ট্রিং literal-এ।

User code:

use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;

#[derive(HelloMacro)]
struct Pancakes;

fn main() {
    Pancakes::hello_macro();
}

Output: Hello, Macro! My name is Pancakes!

Attribute-like macro

Custom derive-এর মতো, কিন্তু শুধু struct/enum না — যেকোনো item-এ attribute হিসেবে বসানো যায়। যেমন একটা web framework-এ:

#[route(GET, "/")]
fn index() {

Definition signature:

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
  • attr — attribute-এর content (GET, "/")।
  • item — যেই item-এ attribute (এখানে fn index() {})।

Function-like macro

Function call-এর মতো দেখতে, কিন্তু macro_rules!-এর চেয়ে flexible — token stream-এ arbitrary processing। যেমন একটা SQL macro:

let sql = sql!(SELECT * FROM posts WHERE id=1);

Definition:

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    // TokenStream-এ SQL parse ও validate
}

এর সুবিধা — pattern matching-এ সীমাবদ্ধ না; arbitrary Rust code দিয়ে input process করতে পারো।

এই পাঠ থেকে যা শিখলে

  • Macro = code-generating code; declarative ও procedural দু'রকম।
  • macro_rules! — pattern matching-ভিত্তিক; variadic ও repetition ($()*) সমর্থন করে।
  • Procedural macro — আলাদা crate-এ; TokenStream-এ arbitrary processing।
  • তিন ধরনের procedural — derive (trait auto-implement), attribute-like (নতুন attribute), function-like (call-like syntax)।
  • syn দিয়ে parse; quote দিয়ে output template; stringify! identifier-কে স্ট্রিং করে।
  • Function-এ যা সম্ভব না — yet code generate করা প্রয়োজন — সেখানে macro; বাকি ক্ষেত্রে সাধারণ function ভালো।