macros/Metaprogramming in Rust?

A macro is code that runs at compile time and generates more Rust code.
Like instructions which generate new code
Examples:
println!(reduces work of variadic arguments)
vec!, #[tokio::main]
#[derive(Debug)](reduces work of custom derives).
Macros are invoked with a ! (function-like) or with #[...] / #![...] (attributes).

Rust vs C macros

C preprocessor (#define) Rust macros
What it does Text substitution before the C compiler sees the file Expands to Rust AST (Abstract syntax tree)
Type checking None on macro body Typechecked
Debugging Hard — error lines point at expansion site oddly Better errors
Hygiene Unhygienic
the macro introduces an identifier (temp) into the caller's code,
and that identifier participates in the caller's normal name lookup.

#define DOUBLE(x) ({ int temp = (x); temp + temp; })
int main() {
    int temp = 2;
    printf("%d\n", DOUBLE(temp));
    return 0;
}

Substitued as
printf("%d\n", ({ 
    int temp = (temp);   // Which "temp" is this?
    temp + temp;
}));
          
Hygienic
macro_rules! twice {
    ($e:expr) => {{
        let tmp = $e;   // this `tmp` belongs to the macro — not your `tmp`
        tmp + tmp
    }};
}

fn main() {
    let tmp = 10;
    let n = twice!(5);   // n = 10, your `tmp` is still 10
    assert_eq!(n, 10);
}

Types of macros in Rust

Type How you write it Examples
Declarative macro_rules! — pattern match on input tokens vec!, println!, your own helpers
Procedural — derive #[derive(TraitName)] on a struct/enum Debug, Clone, Serialize
Procedural — attribute #[some_macro] on item or field #[tokio::main], #[test]
Procedural — function-like my_macro!(...) implemented as proc macro sqlx::query!, custom DSLs

1. Declarative macro — macro_rules!

Match input patterns and substitute
macro_rules! say_hello {
  () => {
      println!("Hello!");
  };
  ($name:expr) => {
      println!("Hello, {}!", $name);
  };
}
fn main() {
    say_hello!();           // Hello!
    say_hello!("Amit");     // Hello, Amit!
}
Convert types before addition
macro_rules! add_as{
    ($a:expr,$b:expr,$typ:ty)=>{
        $a as $typ + $b as $typ
    }
}
fn main(){
    println!("{}",add_as!(0,2,u8));
}
Variable number of arguments
token type that repeats is enclosed in $()
macro_rules! add_as{
  ($($a:expr), *) =>{        //Variable no of argumens
      500         // if no arguments, return 500

      // When arguments are present. +$a is a repeating code.
      $(+$a)*
  }
}
fn main(){
    println!("{}",add_as!(1,2,3,4));        //510
    println!("{}",add_as!());               //500
}
TT(Token Tree) Muncher / Recursive Parsing of Arguments
macro_rules! add{
  ($a:expr)=>{     //1 argument
      $a
  };
  ($a:expr,$b:expr)=>{     //2 arguments
          $a+$b
  };
  ($a:expr, $($b:tt)*)=>{       //more arguments
    //$($b)* is TT Muncher calling next argument incrementally
    $a + add!($($b)*)      
  }
}
fn main(){
    println!("{}",add!(1,2,3,4));    //10
    println!("{}",add!(1));          //1
}
            

2. Procedural macro

A Rust function marked with #[proc_macro], #[proc_macro_derive], or #[proc_macro_attribute]. It takes a TokenStream, builds new tokens (often with the syn + quote crates), and returns expanded code. Lives in a proc-macro = true crate.
We use procedural macros; rarely write them until we need custom derives or domain-specific attributes.

3. Derive macro (built-in / library)

Attach #[derive(...)] to a type; the compiler calls a proc macro that implements a trait for you.
Debug, Clone are standard derives from Rust (and some from crates like serde for Serialize).

struct is annotated using #[derive(MyMacro)] and function is preceded by #[proc_macro_derive]

#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 1, y: 2 };
    println!("{:?}", p);              // Point { x: 1, y: 2 }
    let q = p.clone();
    assert_eq!(p, q);
}

4. Custom derive macro (concept)

Same mechanism as #[derive(Debug)], but you define the trait and the proc macro that implements it — e.g. #[derive(MyTrait)] generates an impl MyTrait for YourStruct { ... }.

// ---- in your library (proc-macro crate) ----
// #[proc_macro_derive(MyTrait)]
// pub fn derive_my_trait(input: TokenStream) -> TokenStream {
//     // parse struct with `syn`, emit impl with `quote`
// }

// ---- in user code ----
// #[derive(MyTrait)]
// struct Config {
//     host: String,
//     port: u16,
// }
// // macro generates: impl MyTrait for Config { ... }

Real projects: serde_derive ( #[derive(Serialize, Deserialize)]), thiserror (#[derive(Error)]). Pattern is always: parse type definition → emit trait implementation.

Quick map

Need simple repetition / DSL in one crate?macro_rules!
Need trait impl from struct fields?#[derive(...)] (procedural derive)
Need to wrap functions or modules? → attribute proc macro (#[tokio::main])

See also: tokio::select! (declarative macro from Tokio).