Skip to content

Macros

Ref - https://doc.rust-lang.org/book/ch20-06-macros.html

In Rust, macros are a powerful feature that allows for metaprogramming by enabling the generation of code at compile-time. Macros in Rust are similar to functions but differ in that they operate at the syntactic level—they generate or transform Rust code before the program is actually compiled.

Key Concepts of Rust Macros:

  1. Code Generation: Macros allow you to define a pattern for generating code. This means you can write code that produces other code, reducing redundancy and boilerplate.
  2. Metaprogramming: Rust macros are a form of metaprogramming because they allow you to write code that writes or manipulates other code. This can be useful for tasks like reducing boilerplate, creating domain-specific languages (DSLs), or automating repetitive patterns.

Types of Macros in Rust

There are two main types of macros in Rust:

Declerative Macros

These are the most common type of macros in Rust. They replace the code written with a different code during compile time

Defining a hello_world macro

macro_rules! say_hello {
() => {
println!("Hello, world!");
};
}
fn main() {
say_hello!(); // Expands to: println!("Hello, world!");
}

Defining a create_function macro

macro_rules! create_function {
($func_name:ident) => {
fn $func_name() {
println!("Hello from {}", stringify!($func_name));
}
};
}
create_function!(hello); // This will create a function called "hello"
fn main() {
hello(); // Prints "Hello from hello"
}

Procedural Macros:

These are more complex macros that allow you to define custom behavior for code generation through Rust code itself. Procedural macros operate on Rust’s Abstract Syntax Tree (AST) and are commonly used for things like deriving traits automatically or creating custom attributes.

#[derive(Serialize, Deserialize)]
struct User {
username: String,
password: String,
age: u32
}

Types of procedural macros

  1. Custom derive macros

Custom derive macros allow you to define how Rust derives certain traits for types. A common use case is generating code for trait implementations (like Debug, Clone, etc.).

#[derive(Serialize, Deserialize)]
struct User {
username: String,
password: String,
age: u32
}
  1. Attribute-like Macros:
#[route("GET")]
fn home() {
println!("Welcome to the home page!");
}
#[route("POST")]
fn create_post() {
println!("Creating a new post!");
}
  1. Function like macros

https://github.com/100xdevs-cohort-3/proc-macro/

Macros applied to attributes

cargo add serde serde_json
// Update serde to use the derive feature
serde = {version = "1.0.218", features = ["derive"]}
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct User {
#[serde(rename = "user_name")]
username: String,
#[serde(rename = "pass_word")]
password: String,
#[serde(rename = "user_age")]
age: u32,
}
fn main() {
let user = User {
username: String::from("Alice"),
password: String::from("password123"),
age: 30,
};
// Serializing to JSON
let json = serde_json::to_string(&user).unwrap();
println!("{}", json);
// Prints: {"user_name":"Alice","pass_word":"password123","user_age":30}
}

See macro extended file

  • cargo install cargo-expand
  • cargo expand

Assignment

  • Ghoda khol assignment

    Write a macro that can take more than one function name as input and create functions for it.

    macro_rules! generate_functions {
    ($($func_name:ident),*) => {
    $(
    fn $func_name() {
    println!("Hello from {}", stringify!($func_name));
    }
    )*
    };
    }
    generate_functions!(foo, bar, baz);
    fn main() {
    foo(); // Prints: Hello from foo
    bar(); // Prints: Hello from bar
    baz(); // Prints: Hello from baz
    }