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.
Macro trong Rust là cách để viết code sinh ra code khác — còn gọi là metaprogramming. Macro cho phép bạn tránh lặp code, tạo DSL (domain-specific languages), và thực hiện những điều mà hàm thông thường không thể làm, như nhận một số lượng tham số biến đổi.
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.
Rust có hai loại macro chính: macro khai báo (declarative macros) sử dụng macro_rules!, và macro thủ tục (procedural macros) hoạt động trên token streams. Macro được mở rộng tại compile time, trước khi kiểm tra kiểu.
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 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 khai báo là loại macro phổ biến nhất trong Rust. Chúng so khớp mẫu (pattern matching) trên code bạn truyền vào và thay thế bằng code khác. Cú pháp trông giống như một biểu thức match nhưng so khớp trên các cấu trúc ngôn ngữ Rust.
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!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 sử dụng các chỉ định đặc biệt (designators) để so khớp các loại cú pháp Rust khác nhau. Ví dụ $e:expr so khớp với biểu thức, $t:ty so khớp với kiểu dữ liệu, $i:ident so khớp với tên định danh. Hiểu các chỉ định này là chìa khóa để viết macro mạnh mẽ.
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 };12Macros 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.
Macro có thể so khớp và sinh ra code lặp lại bằng cú pháp $(...)* hoặc $(...)+. Dấu * nghĩa là khớp không hoặc nhiều lần, dấu + nghĩa là một hoặc nhiều lần. Đây là cách vec! và các macro tương tự chấp nhận số lượng tham số tùy ý.
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}Rust's standard library provides many useful macros. Understanding how they work helps you use them more effectively and write similar ones.
Thư viện chuẩn của Rust cung cấp nhiều macro hữu ích. Hiểu cách chúng hoạt động giúp bạn sử dụng chúng hiệu quả hơn và viết macro tương tự.
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);12Procedural 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.
Macro thủ tục hoạt động khác với macro khai báo: chúng nhận một đoạn code Rust dưới dạng input, xử lý nó bằng code Rust thực sự, và tạo ra code Rust mới. Có ba loại macro thủ tục: macro derive, macro thuộc tính (attribute macros), và macro dạng hàm (function-like macros).
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 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 là loại macro thủ tục phổ biến nhất. Chúng được áp dụng với #[derive(MacroName)] trên một struct hoặc enum và tự động sinh ra code implement trait. Debug, Clone, PartialEq là các derive macro trong thư viện chuẩn.
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); // DebugAttribute 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 tạo các thuộc tính tùy chỉnh có thể được áp dụng cho các items như hàm, struct, và module. Chúng nhận cả attribute và item làm input, và trả về item đã được biến đổi. Tokio dùng #[tokio::main] và #[tokio::test] là các attribute macros.
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 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 procedural macros trông giống như macro_rules! macros khi gọi (dùng dấu !) nhưng được implement bằng code Rust thực sự trong một proc-macro crate. Chúng linh hoạt hơn macro_rules! và có thể xử lý các cú pháp phức tạp hơn.
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// }12Macros 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.
Macro có sức mạnh hơn hàm nhưng cũng phức tạp hơn để viết, gỡ lỗi, và hiểu. Ưu tiên dùng hàm bất cứ khi nào có thể. Chỉ dùng macro khi thực sự cần tính năng mà hàm không thể cung cấp.
- Use functions when: logic is straightforward, easily testable, no need for variable argument count
Dùng hàm khi: logic rõ ràng, có thể kiểm tra dễ dàng, không cần số tham số biến đổi
- Use macros when: variable number of arguments is needed (like println!, vec!)
Dùng macro khi: cần số lượng tham số biến đổi (như println!, vec!)
- Use macros when: code generation based on type structure at compile time is needed (derive macros)
Dùng macro khi: cần sinh code dựa trên cấu trúc kiểu tại compile time (derive macros)
- Use macros when: custom syntax that functions cannot accept is required
Dùng macro khi: cần xử lý cú pháp tùy chỉnh mà hàm không thể nhận
- Use macros when: embedding file contents or environment variables at compile time is needed
Dùng macro khi: cần nhúng nội dung file hoặc biến môi trường tại compile time
- Avoid macros when: a regular function can do the same job — macros are significantly harder to debug
Tránh macro khi: hàm thông thường có thể thực hiện cùng công việc — macro khó gỡ lỗi hơn nhiều
Lỗi biên dịch từ macro thường khó đọc hơn từ hàm vì chúng trỏ đến code được sinh ra thay vì code nguồn của bạn. Macro_rules! cũng không hỗ trợ tốt trong IDE. Cân nhắc kỹ trước khi viết macro phức tạp.
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
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?
What are the three kinds of procedural macros in Rust?
Ba loại macro thủ tục trong Rust là gì?
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.
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.
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 }}; }