Rust: Introdução às macros do Rust
Última atualização: 2026-08-26
As macros são as “máquinas geradoras de código” do Rust — basta escrever uma macro uma vez, e ela gera automaticamente inúmeras instâncias do mesmo código, livrando os programadores da tarefa repetitiva de copiar e colar.
Se uma função “encapsula a lógica para chamadas repetidas”, então uma macro “encapsula regras de geração de código para expansões repetidas”. As funções operam sobre valores em tempo de execução, enquanto as macros operam sobre o próprio código em tempo de compilação. É assim: uma função é uma linha de montagem de fábrica (que recebe matérias-primas e produz produtos), enquanto uma macro é uma planta da fábrica (que recebe desenhos de projeto e produz toda a linha de montagem).
1. O que você vai aprender
macro_rules!Sintaxe básica e regras de correspondência para macros de declaração- Como usar os padrões de repetição
$()*e$()+ - Por que
vec!eprintln!são macros e não funções — a diferença fundamental entre funções e macros Macros integradas comuns:vec!,println!,format!,todo!,unimplemented! - Uma introdução à higiene de macros e ao conceito de macros de processo
2. A história da máquina de imprimir dinheiro
(1) A agonia: o atoleiro do código duplicado
Tom é engenheiro de Rust em uma empresa de logística. Recebeu o que parecia ser uma tarefa simples: escrever uma função para cada um dos cinco meios de transporte (caminhão, navio, avião, trem e drone) para calcular os custos de frete e os prazos estimados de entrega.
- A lógica de cada função é praticamente idêntica: ler a tarifa básica, calcular o fator de distância e adicionar a sobretaxa de combustível
- A única diferença é que cada forma de envio tem uma tabela de tarifas e prazos de entrega diferentes.
- O Tom copiou e colou isso cinco vezes, ajustou um pouco os números — parece que ele conseguiu terminar em 10 minutos.
Então, o que aconteceu?
- No terceiro dia, as regras para o cálculo dos custos com combustível mudaram — Tom precisava modificar cinco funções, mas esqueceu uma.
- Na 5ª semana, foi adicionada a opção “transporte em cadeia de frio” — Tom copiou e colou essa opção pela sexta vez.
- No segundo mês, o código já havia crescido para 300 linhas, das quais 200 eram duplicatas.
“Seria ótimo se houvesse uma maneira de escrever uma regra uma única vez e fazer com que ela gerasse automaticamente todas as funções semelhantes...”
(2) A abordagem das macros em Rust
// Define using a macro"Shipping Cost Calculator"Template
macro_rules! create_shipping_calculator {
// Matching Patterns: Name of Mode of Transportation + Rate per kilometer + Speed
($name:ident, $rate_per_km:expr, $speed:expr) => {
fn $name(distance: f64) -> (f64, f64) {
let base_cost = distance * $rate_per_km;
let fuel_surcharge = base_cost * 0.1;
let total = base_cost + fuel_surcharge;
let time_hours = distance / $speed;
(total, time_hours)
}
};
}
// Single-line macro call = Generate a complete function
create_shipping_calculator!(truck, 1.5, 60.0);
create_shipping_calculator!(ship, 0.8, 30.0);
create_shipping_calculator!(plane, 5.0, 800.0);
create_shipping_calculator!(train, 1.2, 80.0);
create_shipping_calculator!(drone, 2.0, 50.0);
fn main() {
let distance = 500.0;
// Every function exists by default.,Just like handwriting
println!("Truck : cost=${:.2}, time={:.1}h", truck(distance).0, truck(distance).1);
println!("Ship : cost=${:.2}, time={:.1}h", ship(distance).0, ship(distance).1);
println!("Plane : cost=${:.2}, time={:.1}h", plane(distance).0, plane(distance).1);
println!("Train : cost=${:.2}, time={:.1}h", train(distance).0, train(distance).1);
println!("Drone : cost=${:.2}, time={:.1}h", drone(distance).0, drone(distance).1);
}
Resultado:
Truck : cost=$825.00, time=8.3h
Ship : cost=$440.00, time=16.7h
Plane : cost=$2750.00, time=0.6h
Train : cost=$660.00, time=6.2h
Drone : cost=$1100.00, time=10.0h
As macros são como “impressoras de código”: você cria um modelo (a chapa de impressão) e, toda vez que chama a macro, ela “imprime” um trecho completo de código. Quando você faz alterações, basta modificar um único ponto no modelo, e todo o código gerado é atualizado simultaneamente — não é mais necessário modificar manualmente cada função, uma a uma.
3. Conceitos fundamentais
(1) O processo de expansão macroeconômica
graph TB
A[macro_rules! Declaration Macro] --> B[Matching Arm 1: Pattern => Template]
A --> C[Matching Arm 2: Pattern => Template]
A --> D[Matching Arm N: Pattern => Template]
B --> E[Compiler Matching input token]
C --> E
D --> E
E --> F{Match Successful?}
F -->|Yes| G[Replace the template and expand the code]
F -->|No| H[Compilation Error: Mismatch]
G --> I[Generate AST Node]
I --> J[Continue compiling]
style A fill:#4a90d9,color:#fff
style E fill:#e6a23c,color:#fff
style F fill:#f56c6c,color:#fff
style G fill:#67c23a,color:#fff
(2) Comparação entre funções e macros
| Dimensão | Função (fn) | Macro (macro_rules!) |
|---|---|---|
| Momento da execução | Chamada em tempo de execução | Expansão em tempo de compilação |
| Número de parâmetros | Fixo | Variável (por meio de padrões de repetição) |
| Tipo de parâmetro | Tipo fixo | Fluxo de tokens arbitrário |
| Geração de código | Não gera código | Gera uma nova AST de código |
| Valor de retorno | Possui um tipo de retorno | Pode gerar qualquer trecho de código |
| Uso | foo(args) |
foo!(args) com um ponto de exclamação |
| Limites de recursão | Profundidade da pilha | Profundidade de recursão de macros (padrão: 128 níveis) |
| Higiene | Âmbito do isolamento natural | Declaração de higiene parcial macro |
(3) Macros integradas comuns
| Macro | Função | Exemplo |
|---|---|---|
println! |
Imprimir na saída padrão e adicionar uma nova linha | println!("Hello, {}!", name) |
print! |
Imprimir na saída padrão sem caractere de nova linha | print!("count: {}", i) |
format! |
A string formatada retorna um String | let s = format!("{}:{}", h, m) |
vec! |
Criar vetor rapidamente | let v = vec![1, 2, 3] |
todo! |
Espaço reservado, causa um erro grave em tempo de execução | fn foo() { todo!() } |
unimplemented! |
Sinalizador não implementado, erro grave em tempo de execução | fn bar() { unimplemented!() } |
eprintln! |
Imprimir no stderr | eprintln!("Error: {}", msg) |
write! |
Tipos que implementam fmt::Write | write!(&mut s, "{}", val) |
concat! |
Concatenação de strings em tempo de compilação | concat!("a", "b", "c") → "abc" |
stringify! |
Converte uma expressão em uma string literal | stringify!(1+2) → "1 + 2" |
(4) Comparação entre macros de declaração e macros procedurais
| Dimensão | Macro de declaração macro_rules! |
Macro de procedimento (Proc Macro) |
|---|---|---|
| Método de definição | macro_rules! name { ... } |
Caixa independente + #[proc_macro_*] |
| Momento da expansão | Correspondência de padrões + substituição em tempo de compilação | Geração de código procedural em tempo de compilação |
| Recursos | Correspondência de padrões + substituição de tokens | É capaz de ler/gerar qualquer AST |
| Complexidade | Baixa (declarativa) | Alta (requer a escrita de código em Rust para processar a AST) |
| Subcategoria | Nenhuma | Macros derivadas #[derive] / Macros de atributos #[attr] / Macros funcionais name!() |
| Aplicações típicas | vec![], println! |
serde::Serialize, tokio::main |
| Dificuldade de depuração | Moderada | Alta |
4. Exemplos de macros
▶ Exemplo 1: Declarando sua primeira macro — Gerando automaticamente funções getter (Dificuldade ⭐)
// ============================================
// Usage macro_rules! Define a Macro
// Scene: Automatically Generate getter Methods for Structure Fields
// ============================================
// Macro Definitions: Generate getter Function based on field name and type
// A macro name followed by ! Indicates that this is a macro
macro_rules! create_getter {
// Matching Patterns: $name is an identifier (ident), $ty is a type (ty)
// => The code block below is a template expansion.
($name:ident, $ty:ty) => {
pub fn $name(&self) -> $ty {
self.$name.clone()
}
};
}
// Structures That Use Macros
#[derive(Debug)]
struct Student {
name: String,
age: u32,
grade: String,
}
impl Student {
// Written by hand getter —— You have to write one for each field.
// pub fn name(&self) -> String { self.name.clone() }
// pub fn age(&self) -> u32 { self.age }
// pub fn grade(&self) -> String { self.grade.clone() }
// Automatically Generated Using a Macro —— One per line getter
create_getter!(name, String);
create_getter!(age, u32);
create_getter!(grade, String);
}
fn main() {
let s = Student {
name: "Alice".to_string(),
age: 20,
grade: "A".to_string(),
};
// Generated by calling a macro getter Methods
println!("Name : {}", s.name());
println!("Age : {}", s.age());
println!("Grade : {}", s.grade());
}
Resultado:
Name : Alice
Age : 20
Grade : A
A macro
create_getter!recebe um nome de campo e um tipo, e se expande em uma definição completa depub fn. Quando expandida, três chamadas àcreate_getter!equivalem a escrever três funções getter manualmente. Esse é o protótipo da “impressora de código” — o modelo define as regras de geração de código, e cada chamada imprime um trecho de código.
▶ Exemplo 2: Padrões de repetição $()* e $()+ — Macros com parâmetros variáveis (Dificuldade ⭐⭐)
// ============================================
// Repetition Patterns in Presentation Macros:$()* Zero or more times、$()+ Once or multiple times
// Scene: A Mini Test Framework, Supports multiple assertions
// ============================================
// Macro: Run Multiple Test Cases, each case includes a name + Expression + Expected Value
// $()* Indicates that the pattern inside the parentheses can be repeated zero or more times
macro_rules! run_tests {
// Each test consists of (Name, Expression, Expected Value) Composition of Trios
// $test_name It is an identifier,$expr It is an expression,$expected It is an expression
($( $test_name:ident, $expr:expr, $expected:expr );* $(;)?) => {
$(
println!("[Test] {} ...", stringify!($test_name));
let result = $expr;
let expected: i32 = $expected;
if result == expected {
println!(" ✅ PASS: {} == {}", result, expected);
} else {
println!(" ❌ FAIL: {} != {} (expected {})", stringify!($expr), result, expected);
}
)*
};
}
// Macro: Calculate the sum of any number of values
// $()+ Indicates that the pattern inside the parentheses must be repeated at least once
macro_rules! sum_of {
// Usage $()+ At least one argument is required.
($($x:expr),+ $(,)?) => {
// 0 + $x Cumulative total: 0 + a + b + c ...
{
let mut sum = 0i64;
$(
sum += $x as i64;
)+
sum
}
};
}
fn main() {
println!("=== Mini Test Framework ===");
// Call run_tests! macro - Pass multiple test cases
run_tests! {
add_one, 1 + 1, 2;
multiply, 3 * 4, 12;
subtract, 10 - 3, 7;
power, 2 * 2 * 2, 8
}
println!("\n=== Sum Calculator ===");
// Call sum_of! macro - Pass any number of arguments
let s1 = sum_of!(1, 2, 3, 4, 5);
println!("sum_of!(1..5) = {}", s1);
let s2 = sum_of!(10, 20, 30);
println!("sum_of!(10,20,30) = {}", s2);
// A single parameter is also acceptable.
let s3 = sum_of!(42);
println!("sum_of!(42) = {}", s3);
println!("\n=== Done ===");
}
Resultado:
=== Mini Test Framework ===
[Test] add_one ...
✅ PASS: 2 == 2
[Test] multiply ...
✅ PASS: 12 == 12
[Test] subtract ...
✅ PASS: 7 == 7
[Test] power ...
✅ PASS: 8 == 8
=== Sum Calculator ===
sum_of!(1..5) = 15
sum_of!(10,20,30) = 60
sum_of!(42) = 42
=== Done ===
$()*e$()+são fundamentais para implementar “argumentos variáveis” em macros.$()*indica “zero ou mais repetições” (por exemplo,Vecpode ficar vazio), enquanto$()+indica “uma ou mais repetições” (pelo menos um argumento). Dentro dos padrões de repetição, delimitadores (,,;, etc.) também podem ser usados para controlar como os parâmetros são agrupados.
▶ Exemplo 3: Implementação simplificada da macro vec! — Entendendo como funcionam as macros embutidas (Dificuldade ⭐⭐)
// ============================================
// Implement a simplified version of the vec! macro
// Understanding vec! The Underlying Principles of Macro Expansion
// ============================================
// Simplified vec! macro - Does not support vec![x; n] syntax
macro_rules! my_vec {
// Empty vector
() => {
Vec::new()
};
// A single element
($elem:expr) => {
{
let mut v = Vec::new();
v.push($elem);
v
}
};
// Multiple elements,Separated by commas
($($x:expr),+ $(,)?) => {
{
let mut v = Vec::new();
$(
v.push($x);
)+
v
}
};
}
fn main() {
// Use the built-in vec! macro
let builtin_empty: Vec<i32> = vec![];
let builtin_one = vec![42];
let builtin_multi = vec![1, 2, 3, 4, 5];
println!("=== Built-in vec! ===");
println!("empty : {:?}", builtin_empty);
println!("one : {:?}", builtin_one);
println!("multi : {:?}", builtin_multi);
// Use Custom my_vec! macro
let my_empty: Vec<i32> = my_vec![];
let my_one = my_vec![42];
let my_multi = my_vec![10, 20, 30, 40, 50];
println!("\n=== Custom my_vec! ===");
println!("my_empty : {:?}", my_empty);
println!("my_one : {:?}", my_one);
println!("my_multi : {:?}", my_multi);
// Verify Functional Consistency
assert_eq!(builtin_multi.len(), 5);
assert_eq!(my_multi.len(), 5);
assert_eq!(builtin_multi, vec![1, 2, 3, 4, 5]);
assert_eq!(my_multi, vec![10, 20, 30, 40, 50]);
println!("\n=== All assertions passed! ===");
}
Resultado:
=== Built-in vec! ===
empty : []
one : [42]
multi : [1, 2, 3, 4, 5]
=== Custom my_vec! ===
my_empty : []
my_one : [42]
my_multi : [10, 20, 30, 40, 50]
=== All assertions passed! ===
A macro
vec!que você vê é, na verdade, uma macro de declaraçãomacro_rules!! Ela usa$($x:expr),+para corresponder a uma lista de expressões separadas por vírgulas e, em seguida, se expande em blocos repetidos de códigov.push($x). Isso é “algo que uma função não pode fazer” —vec![1, 2, 3]Se fosse escrita como uma função, seria impossível determinar o número de elementos no momento da compilação e gerar o códigopushcorrespondente.
▶ Exemplo 4: Como usar as macros integradas todo! e unimplemented! (Dificuldade: ⭐)
// ============================================
// Demonstrate Built-in Macros: todo! / unimplemented! / format! / eprintln!
// Scene: An inventory management system currently under development
// ============================================
// Simulated Inventory Items
#[derive(Debug)]
struct InventoryItem {
id: u32,
name: String,
quantity: u32,
}
// Inventory Manager
struct InventoryManager {
items: Vec<InventoryItem>,
}
impl InventoryManager {
fn new() -> InventoryManager {
InventoryManager {
items: Vec::new(),
}
}
// Implemented: Add Item
fn add_item(&mut self, id: u32, name: &str, quantity: u32) {
self.items.push(InventoryItem {
id,
name: name.to_string(),
quantity,
});
// Usage format! Macro-Formatted Log Messages
let log_msg = format!("[INFO] Added item: {} (id={}, qty={})", name, id, quantity);
println!("{}", log_msg);
}
// Implemented: Search for Products
fn find_item(&self, id: u32) -> Option<&InventoryItem> {
self.items.iter().find(|item| item.id == id)
}
// Not implemented: Update Inventory
fn update_quantity(&mut self, _id: u32, _new_qty: u32) {
// TODO: Implement the inventory update logic
// todo!() will panic with a "Not implemented" message
todo!("update_quantity: id={} quantity={}", _id, _new_qty);
}
// Not implemented: Generate an Inventory Report
fn generate_report(&self) -> String {
// unimplemented!() Indicates that this feature has not been implemented yet
unimplemented!("generate_report() is not yet implemented");
}
// Implemented: Print All Items
fn list_items(&self) {
if self.items.is_empty() {
println!(" (no items in inventory)");
return;
}
for item in &self.items {
println!(" #{} {} (qty: {})", item.id, item.name, item.quantity);
}
}
}
fn main() {
let mut manager = InventoryManager::new();
println!("=== Inventory Manager ===");
// Add Item
manager.add_item(101, "Laptop", 10);
manager.add_item(102, "Mouse", 50);
manager.add_item(103, "Keyboard", 30);
// List Products
println!("\nCurrent inventory:");
manager.list_items();
// Search for Products
if let Some(item) = manager.find_item(102) {
println!("\nFound: {:?}", item);
}
// Uncommenting the following code will panic (But it won't result in a compilation error):
// manager.update_quantity(101, 8); // panics: "not yet implemented"
// let report = manager.generate_report(); // panics: "not yet implemented"
println!("\n=== Demo completed ===");
println!("Note: Try uncommenting update_quantity() or generate_report() to see todo!/unimplemented! in action.");
}
Resultado:
=== Inventory Manager ===
[INFO] Added item: Laptop (id=101, qty=10)
[INFO] Added item: Mouse (id=102, qty=50)
[INFO] Added item: Keyboard (id=103, qty=30)
Current inventory:
#101 Laptop (qty: 10)
#102 Mouse (qty: 50)
#103 Keyboard (qty: 30)
Found: InventoryItem { id: 102, name: "Mouse", quantity: 50 }
=== Demo completed ===
Note: Try uncommenting update_quantity() or generate_report() to see todo!/unimplemented! in action.
todo!()eunimplemented!()são “poderosas ferramentas de espaço reservado” para desenvolvedores de Rust.todo!()pode incluir uma descrição (todo!("msg: {}", val)), tornando-o ideal para marcar funcionalidades ainda não concluídas durante o desenvolvimento.unimplemented!()é mais adequado para marcar métodos em definições de interface que ainda não foram implementados. Ambos causarão um panic em tempo de execução, mas não resultarão em erros de compilação — permitindo que você escreva o código primeiro e o implemente passo a passo.
▶ Exemplo 5: Higiene de macros — Variáveis internas de macros não contaminam o ambiente externo (Dificuldade ⭐⭐⭐)
// ============================================
// Demonstrating Macro Hygiene
// Variables created within a macro do not conflict with those in the outer scope.
// ============================================
// Note: Rust declaration macros have "partial hygiene"
// For internal use by Hong $ Captured variable names will not conflict with external ones
// However, identifiers written directly within the macro are in Rust 2018+ There are special rules in this case
// Macro: Create a local variable tmp and swap values
// Note: tmp written directly within the macro is's hygienic.——It will not affect the outside world. tmp
macro_rules! swap_with_tmp {
($a:expr, $b:expr) => {
{
let tmp = $a;
$a = $b;
$b = tmp;
}
};
}
// Macro: Demonstrate Hygiene - Even if there is a tmp variable outside
macro_rules! demonstrate_hygiene {
($x:expr) => {
{
// Defined internally by the macro tmp It's hygienic.
let tmp = $x * 2;
println!(" Inside macro: tmp = {}", tmp);
tmp
}
};
}
// Counterexample of Unhygienic Conditions (Demonstrated differently)
// Note: Rust declarative macros do not allow creating variables that cause cross-scope conflicts.
// So here we use concat Simulation"Non-health-related"Potential Issues
fn main() {
println!("=== Macro Hygiene Demonstration ===\n");
// Scenario 1: Internal variables in a macro do not affect external variables
println!("1. Macro internal variable vs external variable:");
let mut x = 10;
let mut y = 20;
println!(" Before swap: x={}, y={}", x, y);
// The macro uses the following internally: tmp,But the external variable names tmp Not affected
swap_with_tmp!(x, y);
println!(" After swap: x={}, y={}", x, y);
// External tmp The variable does not exist. —— Inside the macro tmp It's hygienic.
// If you uncomment the following line, you'll get a compilation error.:
// println!("tmp from macro = {}", tmp); // ❌ Compilation Error: tmp not found
// Scenario 2: Internal macro variables do not conflict with external variables of the same name
println!("\n2. Hygiene with same name:");
let tmp = 100; // External tmp
println!(" Outside macro: tmp = {}", tmp);
let result = demonstrate_hygiene!(5);
println!(" Return value: {}", result);
println!(" Outside macro again: tmp = {}", tmp); // It's still 100,Not affected by macros
// Scenario 3: Why Is Hygiene Important?
println!("\n3. Why hygiene matters:");
println!(" Without hygiene, macros could accidentally:");
println!(" - Overwrite variables in the caller's scope");
println!(" - Create hard-to-find bugs");
println!(" - Break encapsulation of the calling code");
println!(" Rust's hygiene prevents these issues at compile time.");
println!("\n=== Demo completed ===");
}
Resultado:
=== Macro Hygiene Demonstration ===
1. Macro internal variable vs external variable:
Before swap: x=10, y=20
After swap: x=20, y=10
2. Hygiene with same name:
Outside macro: tmp = 100
Inside macro: tmp = 10
Return value: 10
Outside macro again: tmp = 100
3. Why hygiene matters:
Without hygiene, macros could accidentally:
- Overwrite variables in the caller's scope
- Create hard-to-find bugs
- Break encapsulation of the calling code
Rust's hygiene prevents these issues at compile time.
A higiene de macros é uma característica importante das macros do Rust: os nomes de variáveis criados dentro de uma macro não “vazam” para o escopo do chamador. No exemplo acima, há um
tmpdentro da macro e umtmpfora dela, mas eles não interferem um no outro. Isso evita os problemas de “conflito de nomes” comuns nas macros do C — no C, se uma macro usa uma variável chamadatmpe o chamador por acaso tiver uma variável chamadatmp, isso pode levar a bugs difíceis de depurar.
▶ Exemplo 6: Exemplo abrangente — Criação de uma mini-estrutura de testes com macros (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Example: Building a Mini Unit Testing Framework Using Macros
// Use in combination:Matching Patterns、Repetition Pattern、Built-in Macros
// ============================================
// Test Results Summary
struct TestStats {
total: u32,
passed: u32,
failed: u32,
}
impl TestStats {
fn new() -> TestStats {
TestStats { total: 0, passed: 0, failed: 0 }
}
fn print_summary(&self) {
println!("\n==============================");
println!("Test Summary:");
println!(" Total : {}", self.total);
println!(" Passed: {}", self.passed);
println!(" Failed: {}", self.failed);
if self.failed == 0 {
println!(" ✅ All tests passed!");
} else {
println!(" ❌ {} test(s) failed", self.failed);
}
println!("==============================");
}
}
// Macro: Define a set of test cases
// Each test is identified by its name、Assertion Expressions、Expected Results Breakdown
macro_rules! test_suite {
// Matches zero or more tests
($( $name:ident: $left:expr, $op:tt, $right:expr );* $(;)?) => {{
let mut stats = TestStats::new();
$(
stats.total += 1;
print!("[Test] {}: {} {} {} ... ", stringify!($name),
stringify!($left), stringify!($op), stringify!($right));
let passed = match $op {
== => { $left == $right }
!= => { $left != $right }
< => { $left < $right }
<= => { $left <= $right }
> => { $left > $right }
>= => { $left >= $right }
_ => { panic!("Unsupported operator: {}", stringify!($op)); }
};
if passed {
stats.passed += 1;
println!("✅ PASS");
} else {
stats.failed += 1;
println!("❌ FAIL (got {:?}, expected {:?})", $left, $right);
}
)*
stats
}};
}
fn main() {
println!("=== Mini Test Framework ===");
println!("Using macro-generated test suite\n");
// Test Suites Defined Using Macros
let stats = test_suite! {
test_add: 2 + 2, ==, 4;
test_sub: 10 - 3, ==, 7;
test_mul: 3 * 4, ==, 12;
test_div: 10 / 2, ==, 5;
test_gt: 100, >, 50;
test_lt: 3, <, 10;
test_eq: "hello", ==, "hello";
test_neq: 42, !=, 0
};
// Print Statistics
stats.print_summary();
// Verify that all tests have passed
assert_eq!(stats.total, 8);
assert_eq!(stats.passed, 8);
assert_eq!(stats.failed, 0);
println!("\n=== Demo completed ===");
}
Resultado:
=== Mini Test Framework ===
Using macro-generated test suite
[Test] test_add: 2 + 2 == 4 ... ✅ PASS
[Test] test_sub: 10 - 3 == 7 ... ✅ PASS
[Test] test_mul: 3 * 4 == 12 ... ✅ PASS
[Test] test_div: 10 / 2 == 5 ... ✅ PASS
[Test] test_gt: 100 > 50 ... ✅ PASS
[Test] test_lt: 3 < 10 ... ✅ PASS
[Test] test_eq: "hello" == "hello" ... ✅ PASS
[Test] test_neq: 42 != 0 ... ✅ PASS
==============================
Test Summary:
Total : 8
Passed: 8
Failed: 0
✅ All tests passed!
==============================
=== Demo completed ===
Este exemplo abrangente demonstra o verdadeiro poder das macros: a macro
test_suite!recebe um conjunto de definições de teste (nome, expressão, operador de comparação, valor esperado) e as expande automaticamente em código completo de execução de teste. Oito casos de teste escritos usando a macro requerem apenas oito linhas de código para serem chamados, enquanto escrever o código equivalente manualmente exigiria pelo menos 60 linhas. Mais importante ainda, se você precisar adicionar recursos como “tempos limite de teste” ou “agrupamento de testes”, basta modificar um único ponto no modelo da macro, e todos os testes serão atualizados automaticamente.
❓ Perguntas Frequentes
P: Qual é exatamente a diferença entre macros e funções? Quando se deve usar uma macro em vez de uma função? R: As macros expandem seu código em tempo de compilação, enquanto as funções são executadas em tempo de execução. Macros podem fazer coisas que funções não conseguem: lidar com um número variável de argumentos (como
vec![1, 2, 3]), gerar código na fase de compilação e aceitar trechos de código como argumentos. No entanto, macros são mais difíceis de depurar, resultam em código menos legível e podem levar a mensagens de erro obscuras na fase de compilação. Priorize o uso de funções e use macros apenas quando precisar fazer algo que as funções não conseguem.
P: Qual é a diferença entre
$()*e$()+emmacro_rules!? R:$()*corresponde a zero ou mais repetições (incluindo uma string vazia), enquanto$()+corresponde a uma ou mais repetições (pelo menos uma). Por exemplo,vec usa$()*, enquantovec![1, 2]pode usar tanto$()+quanto$()*. Se você quiser que a macro aceite pelo menos um argumento, use$()+; se permitir zero argumento, use$()*.
P: O que é higiene de macros? Por que ela é importante? R: A higiene se refere ao fato de que as variáveis criadas dentro de uma macro não entram em conflito com as variáveis do escopo externo. Em C, as macros são substituições de texto, portanto, conflitos de nomes podem surgir facilmente — por exemplo, se uma macro usa
tmpinternamente, mas o chamador também tiver uma variável chamadatmp. As macros de declaração do Rust são “parcialmente higiênicas”: os nomes de variáveis capturados por$dentro da macro não vazam, evitando assim esses bugs.
P: Qual é a diferença entre
todo!()eunimplemented!()? R: Ambos causam um erro de pânico em tempo de execução, mas sua semântica difere.todo!()indica que “esse recurso está planejado, mas ainda não foi implementado” e pode incluir parâmetros para especificar o progresso (por exemplo,todo!("implement pagination")).unimplemented!()indica que “esta interface/método não está planejado para implementação no momento”.todo!()é mais comumente usado durante o desenvolvimento para marcar itens na lista de tarefas, enquantounimplemented!()é mais comumente usado em implementações padrão de traits.
P: O que é uma macro procedural? R: Uma macro procedural é um sistema de macros mais poderoso do que as macros declarativas; ela não realiza “correspondência e substituição de padrões”, mas sim “recebe código → executa uma função em Rust → gera um novo código”. Existem três tipos: macros derivadas
#[derive(...)](como#[derive(Debug)]), macros de atributos (como#[test]) e macros de função (como#[async], que está por trás deasync fn). As macros procedimentais devem ser definidas em um crateproc-macroseparado, enquanto as macros de declaraçãomacro_rules!podem ser definidas em qualquer lugar. Esta lição aborda apenas os conceitos; o uso detalhado das macros procedimentais é um assunto avançado.
📖 Resumo
macro_rules!é o sistema de macros declarativas do Rust, que utiliza a correspondência de padrões para substituir um fluxo de tokens de entrada por modelos de código que são expandidos em tempo de compilação.- Padrões de repetição
$()*(zero ou mais vezes) e$()+(uma ou mais vezes) são os mecanismos centrais para a implementação de macros com argumentos variáveis. - Macros integradas comuns
vec!,println!,format!,todo!eunimplemented!são, essencialmente, declarações de macro;vec!se expande paraVec::new(), seguido por uma repetição depush. - Funções x Macros: As funções operam sobre valores em tempo de execução, enquanto as macros operam sobre o código em tempo de compilação; os parâmetros das funções são fixos, enquanto os parâmetros das macros são variáveis; as funções realizam a verificação de tipos, enquanto as macros só passam por essa verificação após a expansão.
- Higiene de macros garante que as variáveis macro internas não vazem para o escopo externo, evitando assim conflitos de nomes com as macros da linguagem C.
- Macros de processo são um sistema de macros mais avançado que inclui três tipos: macros derivadas, macros de atributos e macros de função. Elas devem ser definidas em um crate
proc-macroseparado.
📝 Exercícios
- Dificuldade ⭐: Escreva uma macro
make_pair!que receba duas expressões como argumentos e retorne uma tupla(expr1, expr2). Por exemplo,make_pair!(42, "hello")se expande para(42, "hello"). Chame-a emmaine imprima o resultado. - Dificuldade ⭐⭐: Escreva uma macro
assert_equal!que receba duas expressões como argumentos. Se elas forem iguais, exiba✅ PASS; caso contrário, exiba❌ FAIL: left != right. Use a macrostringify!para exibir as expressões originais. Crie pelo menos 3 casos de teste (incluindo casos em que as expressões sejam iguais e casos em que não sejam) e execute-os. - Dificuldade ⭐⭐⭐: Escreva uma macro
create_enum_with_display!que receba como entrada um nome de enumeração e um conjunto de nomes de variantes, e gere automaticamente a definição da enumeração e a implementação da característicaDisplay(com cada variante exibida como sua string correspondente). Por exemplo,create_enum_with_display!(Color, Red, Green, Blue)se expande em uma enumeraçãoColor, na qualRedé exibido como"Red". Dica: use o padrão de repetição$()*estringify!.