Skip to content
DocsRust LearningexpertMacros
Chapter 16 of 19·expert·9 min read

Macros

Macro

Declarative and procedural macros

Hover or tap any paragraph to see Vietnamese translation

What Are Macros and Why Use Them

Macros in Rust are a way of writing code that generates other code — known as metaprogramming. Macros let you avoid code repetition, create domain-specific languages (DSLs), and do things regular functions cannot, like accepting a variable number of arguments.

Rust has two major kinds of macros: declarative macros using macro_rules!, and procedural macros that operate on token streams. Macros are expanded at compile time, before type checking.

why_macros.rs
1// Why macros? Functions cannot do this:2fn add_two_args(a: i32, b: i32) -> i32 { a + b }3// add_two_args(1, 2, 3)  // compile error: too many arguments45// But macros can handle variable arguments:6// println!("one value: {}", x);7// println!("two values: {} {}", x, y);89// Functions cannot be variadic in Rust.10// Macros can.1112// Also, macros can take different KINDS of arguments:

Declarative Macros with macro_rules!

Declarative macros are the most common kind of macro in Rust. They pattern-match on the code you pass them and replace it with different code. The syntax looks like a match expression but matches on Rust language constructs.

macro_rules_basics.rs
1// Defining a simple macro2macro_rules! say_hello {3    () => {4        println!("Hello!");5    };6    ($name:expr) => {7        println!("Hello, {}!", $name);8    };9}1011fn main() {12    say_hello!();           // Hello!

Pattern Matching in Macros

Macros use special designators to match different kinds of Rust syntax. For example, $e:expr matches an expression, $t:ty matches a type, $i:ident matches an identifier name. Understanding these designators is key to writing powerful macros.

macro_designators.rs
1macro_rules! demonstrate_designators {2    // expr: any Rust expression3    (expr: $e:expr) => {4        println!("Expression result: {}", $e);5    };67    // ident: an identifier (variable name, function name, etc.)8    (ident: $i:ident) => {9        let $i = 42;10        println!("Created variable with value: {}", $i);11    };12

Repetition Patterns

Macros can match and generate repeated code using the $(...)* or $(...)+ syntax. An asterisk means zero or more times, a plus means one or more times. This is how vec! and similar macros accept any number of arguments.

repetition_patterns.rs
1// Implementing our own vec! macro2macro_rules! my_vec {3    // Match zero or more comma-separated expressions4    ($($element:expr),* $(,)?) => {5        {6            let mut v = Vec::new();7            // Expand the repetition: run once per element8            $(v.push($element);)*9            v10        }11    };12}

Common Standard Library Macros

Rust's standard library provides many useful macros. Understanding how they work helps you use them more effectively and write similar ones.

standard_macros.rs
1fn main() {2    // println! / eprintln! / format! - formatted output3    let name = "Rust";4    println!("Hello, {}!", name);5    eprintln!("Error message to stderr");6    let s = format!("Value: {}", 42);78    // assert! / assert_eq! / assert_ne! - panics if condition fails9    assert!(1 + 1 == 2);10    assert_eq!(2 + 2, 4, "math should work");11    assert_ne!(1, 2);12

Procedural Macros Overview

Procedural macros work differently from declarative macros: they take a piece of Rust code as input, process it using actual Rust code, and produce new Rust code. There are three kinds of procedural macros: derive macros, attribute macros, and function-like macros.

proc_macro_overview.rs
1// Procedural macros live in their own crate with:2// [lib]3// proc-macro = true45// They use the proc_macro crate and usually proc-macro2, syn, quote:6// [dependencies]7// proc-macro2 = "1"8// quote = "1"9// syn = { version = "2", features = ["full"] }1011// A minimal proc macro crate structure:12// my_macros/

Derive Macros

Derive macros are the most common kind of procedural macro. They are applied with #[derive(MacroName)] on a struct or enum and automatically generate trait implementation code. Debug, Clone, and PartialEq are derive macros in the standard library.

derive_macros.rs
1// Built-in derive macros from std2#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]3struct Point {4    x: i32,5    y: i32,6}78fn main() {9    let p1 = Point { x: 1, y: 2 };10    let p2 = p1.clone();1112    println!("{:?}", p1);       // Debug

Attribute Macros

Attribute macros create custom attributes that can be applied to items like functions, structs, and modules. They receive both the attribute and the item as input, and return the transformed item. Tokio's #[tokio::main] and #[tokio::test] are attribute macros.

attribute_macros.rs
1// Using built-in and popular attribute macros:23// #[test] - marks a function as a test4#[test]5fn it_works() {6    assert_eq!(2 + 2, 4);7}89// #[tokio::test] - async test with tokio runtime10// [dev-dependencies]11// tokio = { version = "1", features = ["full"] }12#[tokio::test]

Function-like Procedural Macros

Function-like procedural macros look like macro_rules! macros when called (using the ! operator) but are implemented using real Rust code in a proc-macro crate. They are more flexible than macro_rules! and can handle more complex syntax.

function_like_proc_macros.rs
1// Example: sql! macro (like in diesel or sqlx)2// sql!("SELECT * FROM users WHERE id = ?", user_id)3// Validates SQL at compile time and returns typed results45// Example: html! macro (like in yew framework)6// html! {7//     <div class="container">8//         <h1>{ title }</h1>9//         <p>{ body }</p>10//     </div>11// }12

When to Use Macros vs Functions

Macros are more powerful than functions but also harder to write, debug, and understand. Prefer functions whenever possible. Only use macros when you genuinely need capabilities that functions cannot provide.

  • Use functions when: logic is straightforward, easily testable, no need for variable argument count
  • Use macros when: variable number of arguments is needed (like println!, vec!)
  • Use macros when: code generation based on type structure at compile time is needed (derive macros)
  • Use macros when: custom syntax that functions cannot accept is required
  • Use macros when: embedding file contents or environment variables at compile time is needed
  • Avoid macros when: a regular function can do the same job — macros are significantly harder to debug
Warning
Compile errors from macros are often harder to read than from functions because they point to generated code rather than your source. macro_rules! also has poor IDE support. Think carefully before writing complex macros.

Key Takeaways

Điểm Chính

  • Declarative macros (macro_rules!) match patterns and expand codeMacro khai báo (macro_rules!) khớp mẫu và mở rộng code
  • Procedural macros operate on token streams at compile timeMacro thủ tục hoạt động trên luồng token tại thời điểm biên dịch
  • derive macros auto-implement traits like Debug, Clone, SerializeMacro derive tự triển khai trait như Debug, Clone, Serialize
  • Macros are expanded before type checking, unlike functionsMacro được mở rộng trước kiểm tra kiểu, khác với hàm

Practice

Test your understanding of this chapter

Quiz

Which macro designator matches a single identifier name such as a variable or function name?

Chỉ định macro nào khớp với tên định danh đơn như tên biến hoặc tên hàm?

Quiz

What are the three kinds of procedural macros in Rust?

Ba loại macro thủ tục trong Rust là gì?

True or False

Rust macros are expanded at runtime, after type checking has been performed.

Macro Rust được mở rộng tại runtime, sau khi kiểm tra kiểu đã được thực hiện.

True or False

In macro_rules!, the $(...)* syntax matches zero or more repetitions, while $(...)+ requires at least one repetition.

Trong macro_rules!, cú pháp $(...)* khớp với không hoặc nhiều lần lặp, trong khi $(...)+ yêu cầu ít nhất một lần lặp.

Code Challenge

Complete the macro designator to match any Rust expression

Hoàn thành chỉ định macro để khớp với bất kỳ biểu thức Rust nào

macro_rules! my_vec {
    ($($element:),*) => {{
        let mut v = Vec::new();
        $(v.push($element);)*
        v
    }};
}

Chapter Complete!

Great job! Keep the momentum going.

Your progress0 of 19 chapters read
← → to navigate chapters
Built: 4/8/2026, 12:01:11 PM