Macros#

Rust has two kinds of macros: declarative macros (macro_rules!) for pattern-based code generation, and procedural macros for AST-level transformations at compile time. Both are more powerful and safer than C/C++ preprocessor macros.

Built-in Attributes#

Source:

src/rust/proc_macros

Attributes are metadata applied to items using #[...]. They control compilation, generate code, or suppress warnings.

C++:

// Conditional compilation
#ifdef _WIN32
void platform() { std::cout << "windows"; }
#else
void platform() { std::cout << "other"; }
#endif

// Suppress warnings
#pragma warning(disable: 4101)  // MSVC
[[maybe_unused]] int x = 5;     // C++17

// Inline hint
inline int add(int a, int b) { return a + b; }

Rust:

// Conditional compilation
#[cfg(target_os = "windows")]
fn platform() -> &'static str { "windows" }

#[cfg(not(target_os = "windows"))]
fn platform() -> &'static str { "other" }

// Suppress warnings
#[allow(dead_code)]
fn unused_function() -> i32 { 42 }

// Inline hint
#[inline]
fn add(a: i32, b: i32) -> i32 { a + b }

Common built-in attributes:

#[derive(Debug, Clone, PartialEq)]  // auto-generate trait impls
#[cfg(test)]                         // only compile in test builds
#[test]                              // mark as test function
#[allow(unused_variables)]           // suppress warning
#[inline]                            // inline hint
#[must_use]                          // warn if return value is ignored

Derive Macros#

#[derive] auto-generates trait implementations. This has no C++ equivalent — C++ requires manually writing operators, copy constructors, etc.

C++ (manual implementation):

#include <iostream>

struct Point {
    double x, y;

    // Must manually implement operator==
    bool operator==(const Point& other) const {
        return x == other.x && y == other.y;
    }

    // Must manually implement operator<< for printing
    friend std::ostream& operator<<(std::ostream& os, const Point& p) {
        return os << "Point { x: " << p.x << ", y: " << p.y << " }";
    }

    // Copy constructor/assignment generated by default in C++
};

Rust (auto-generated with derive):

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

fn main() {
    let p = Point { x: 1.0, y: 2.0 };
    let p2 = p.clone();          // Clone
    println!("{:?}", p);          // Debug
    assert_eq!(p, p2);            // PartialEq
}

Common derivable traits:

#[derive(
    Debug,       // {:?} formatting
    Clone,       // .clone() method
    Copy,        // implicit copy (like C++ trivial types)
    PartialEq,   // == and !=
    Eq,          // stricter equality (no NaN issues)
    Hash,        // for use in HashMap keys
    Default,     // Default::default() with zero/empty values
)]

Declarative Macros (macro_rules!)#

Source:

src/rust/proc_macros

Declarative macros use pattern matching to generate code. Unlike C++ #define, they are hygienic (no name collisions) and type-aware.

C++ (preprocessor macros):

// Simple macro - text substitution, no type safety
#define SAY_HELLO() std::cout << "Hello!" << std::endl

// Macro with argument - prone to double evaluation bugs
#define MAX(a, b) ((a) > (b) ? (a) : (b))

// Variadic macro
#define LOG(fmt, ...) printf(fmt, __VA_ARGS__)

Rust (declarative macros):

// Simple macro
macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
    ($name:expr) => {
        println!("Hello, {}!", $name);
    };
}

say_hello!();          // Hello!
say_hello!("Rust");    // Hello, Rust!

Repetition patterns — no C++ equivalent:

// $(...),* matches zero or more comma-separated items
macro_rules! vec_of_strings {
    ($($x:expr),* $(,)?) => {
        vec![$($x.to_string()),*]
    };
}

let v = vec_of_strings!["a", "b", "c"];
// expands to: vec!["a".to_string(), "b".to_string(), "c".to_string()]

Generating functions with macros:

macro_rules! make_adder {
    ($name:ident, $t:ty) => {
        fn $name(a: $t, b: $t) -> $t {
            a + b
        }
    };
}

make_adder!(add_i32, i32);
make_adder!(add_f64, f64);

assert_eq!(add_i32(1, 2), 3);
assert_eq!(add_f64(1.0, 2.0), 4.0);

Macro fragment types:

// $x:expr  - expression (1 + 2, foo(), "hello")
// $x:ident - identifier (variable/function name)
// $x:ty    - type (i32, String, Vec<u8>)
// $x:pat   - pattern (Some(x), (a, b))
// $x:stmt  - statement (let x = 1;)
// $x:block - block ({ ... })
// $x:tt    - single token tree (any token)

Procedural Macros#

Procedural macros operate on the token stream (AST) at compile time. They are more powerful than macro_rules! but must be defined in a separate crate with proc-macro = true.

There are three types:

// 1. Attribute macros — #[something]
//    Transforms the annotated item
#[proc_macro_attribute]
pub fn my_attr(args: TokenStream, item: TokenStream) -> TokenStream { ... }
// Usage: #[my_attr]

// 2. Derive macros — #[derive(Something)]
//    Adds code alongside the annotated item
#[proc_macro_derive(MyTrait)]
pub fn my_derive(input: TokenStream) -> TokenStream { ... }
// Usage: #[derive(MyTrait)]

// 3. Function-like macros — something!(...)
//    Called like a function
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream { ... }
// Usage: sql!(SELECT * FROM users)

Crate setup for procedural macros:

# Cargo.toml for a proc macro crate
[lib]
proc-macro = true

[dependencies]
syn = "2"           # parse Rust syntax
quote = "1"         # generate Rust code
proc-macro2 = "1"   # TokenStream utilities

Real-world example — #[tokio::main] expands async main:

// What you write:
#[tokio::main]
async fn main() {
    do_stuff().await;
}

// What it expands to:
fn main() {
    tokio::runtime::Runtime::new()
        .unwrap()
        .block_on(async {
            do_stuff().await;
        });
}

C++ has no equivalent to procedural macros. The C preprocessor does text substitution, while Rust proc macros parse and transform actual syntax trees at compile time.

Comparison Summary#

Feature

Rust

C++

Simple macros

macro_rules! (hygienic, type-aware)

#define (text substitution)

Conditional compilation

#[cfg(...)]

#ifdef / #if

Auto-generate code

#[derive(Trait)]

No equivalent

AST transformation

Procedural macros

No equivalent

Suppress warnings

#[allow(...)]

#pragma / [[maybe_unused]]

Inline hint

#[inline]

inline keyword

See Also#