Desde o primeiro artigo usamos macros sem entender completamente o que são: println!, vec!, assert_eq!, derive. O ponto de exclamação no final de println! não é ornamento — é a marca de uma macro. Chegou a hora de entender o que são macros em Rust, por que existem, e como escrever as suas próprias.
Macros em Rust são um sistema de metaprogramação: código que gera código em tempo de compilação. São mais poderosas que macros de C (simples substituição de texto), mais seguras, e integradas ao sistema de tipos.
Por que macros existem
Macros resolvem problemas que funções não conseguem resolver. Há três razões principais:
Número variável de argumentos. println!("x={}, y={}", x, y) aceita qualquer número de argumentos. Funções Rust têm aridade fixa — você não pode criar uma função imprimir que aceite 0, 1, 2 ou 10 argumentos. Macros podem.
Geração de código repetitivo. #[derive(Debug, Clone, PartialEq)] gera implementações inteiras de traits automaticamente. Escrever isso manualmente para cada struct seria tedioso e propenso a erros.
DSLs (Domain Specific Languages). Macros permitem criar sintaxes especializadas embutidas no Rust — como a sintaxe de vec![1, 2, 3] que parece parte da linguagem mas é uma macro.
Dois tipos de macros
Rust tem dois sistemas de macros distintos:
Macros declarativas (macro_rules!) — definem padrões de correspondência. São como match para código: se o código de entrada corresponde ao padrão, é substituído pelo código de saída.
Macros procedurais — são funções Rust que operam sobre tokens do compilador. Mais poderosas e mais complexas. Incluem as macros derive, macros de atributo e macros de função.
Neste artigo focamos em macro_rules! — o ponto de entrada mais acessível e suficiente para a maioria dos casos de uso.
macro_rules! — macros declarativas
A sintaxe básica:
macro_rules! nome_da_macro {
(padrão1) => {
código_gerado1
};
(padrão2) => {
código_gerado2
};
}
Vamos começar com o exemplo mais simples — uma macro que não faz nada útil mas demonstra a sintaxe:
macro_rules! dizer_ola {
() => {
println!("Olá do macro!");
};
}
fn main() {
dizer_ola!();
dizer_ola!();
}
Saída:
Olá do macro!
Olá do macro!
Capturando argumentos
Macros podem capturar argumentos com designadores de tipo:
macro_rules! inspecionar {
($valor:expr) => {
println!("{} = {:?}", stringify!($valor), $valor);
};
}
fn main() {
let x = 42;
let nome = "Rust";
let lista = vec![1, 2, 3];
inspecionar!(x);
inspecionar!(nome);
inspecionar!(lista);
inspecionar!(2 + 2);
inspecionar!(x * 3 + 1);
}
Saída:
x = 42
nome = "Rust"
lista = [1, 2, 3]
2 + 2 = 4
x * 3 + 1 = 127
$valor:expr captura uma expressão. Os designadores disponíveis incluem:
expr— qualquer expressãoident— um identificador (nome de variável ou função)ty— um tipoliteral— um literal (número, string)pat— um padrão (como emmatch)stmt— um statementblock— um bloco{ ... }tt— qualquer token tree (o mais genérico)
Múltiplos padrões
Macros podem ter múltiplos braços, como match:
macro_rules! criar_vetor {
// Sem argumentos — vetor vazio
() => {
Vec::new()
};
// Um único elemento
($elem:expr) => {
{
let mut v = Vec::new();
v.push($elem);
v
}
};
// Elemento repetido N vezes
($elem:expr; $n:expr) => {
{
let mut v = Vec::with_capacity($n);
for _ in 0..$n {
v.push($elem.clone());
}
v
}
};
// Lista de elementos
($($elem:expr),+ $(,)?) => {
{
let mut v = Vec::new();
$(v.push($elem);)+
v
}
};
}
fn main() {
let v1: Vec<i32> = criar_vetor!();
let v2 = criar_vetor!(42);
let v3 = criar_vetor!(0; 5);
let v4 = criar_vetor![1, 2, 3, 4, 5];
let v5 = criar_vetor![10, 20, 30,]; // vírgula final aceita
println!("{:?}", v1); // []
println!("{:?}", v2); // [42]
println!("{:?}", v3); // [0, 0, 0, 0, 0]
println!("{:?}", v4); // [1, 2, 3, 4, 5]
println!("{:?}", v5); // [10, 20, 30]
}
O padrão $($elem:expr),+ significa "um ou mais expr separados por vírgulas". O + indica "um ou mais", enquanto * indica "zero ou mais". O $(,)? no final aceita uma vírgula trailing opcional.
Recursão em macros
Macros podem se chamar recursivamente — útil para processar listas de argumentos:
macro_rules! somar {
// Caso base: um único número
($x:expr) => ($x);
// Caso recursivo: primeiro + resto
($x:expr, $($resto:expr),+) => {
$x + somar!($($resto),+)
};
}
macro_rules! maximo {
($x:expr) => ($x);
($x:expr, $($resto:expr),+) => {
{
let resto_max = maximo!($($resto),+);
if $x > resto_max { $x } else { resto_max }
}
};
}
fn main() {
println!("{}", somar!(1)); // 1
println!("{}", somar!(1, 2, 3)); // 6
println!("{}", somar!(10, 20, 30, 40)); // 100
println!("{}", maximo!(5)); // 5
println!("{}", maximo!(3, 7, 2, 9, 1)); // 9
}
Uma macro prática: hashmap!
A biblioteca padrão tem vec! mas não tem hashmap!. Vamos criar:
use std::collections::HashMap;
macro_rules! hashmap {
// Vazio
() => {
HashMap::new()
};
// Com pares chave => valor
($($chave:expr => $valor:expr),+ $(,)?) => {
{
let mut mapa = HashMap::new();
$(mapa.insert($chave, $valor);)+
mapa
}
};
}
fn main() {
let vazio: HashMap<&str, i32> = hashmap!();
let capitais = hashmap! {
"Brasil" => "Brasília",
"Argentina" => "Buenos Aires",
"Chile" => "Santiago",
"Uruguai" => "Montevidéu",
};
let notas = hashmap! {
"Ana" => 9.5,
"Carlos" => 7.2,
"Maria" => 8.8,
};
println!("Vazio: {:?}", vazio);
println!("
Capitais:");
let mut pares: Vec<_> = capitais.iter().collect();
pares.sort_by_key(|&(k, _)| k);
for (pais, capital) in pares {
println!(" {pais}: {capital}");
}
println!("
Notas:");
let mut ranking: Vec<_> = notas.iter().collect();
ranking.sort_by(|a, b| b.1.partial_cmp(a.1).unwrap());
for (aluno, nota) in ranking {
println!(" {aluno}: {nota:.1}");
}
}
Saída:
Vazio: {}
Capitais:
Argentina: Buenos Aires
Brasil: Brasília
Chile: Santiago
Uruguai: Montevidéu
Notas:
Ana: 9.5
Maria: 8.8
Carlos: 7.2
Macros para geração de código repetitivo
Um dos usos mais poderosos de macros é eliminar código boilerplate. Veja um exemplo que gera implementações de conversão de unidades:
macro_rules! unidade {
($nome:ident, $simbolo:expr, $para_base:expr, $da_base:expr) => {
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
struct $nome(f64);
impl $nome {
fn novo(valor: f64) -> Self {
$nome(valor)
}
fn valor(&self) -> f64 {
self.0
}
fn para_base(&self) -> f64 {
$para_base(self.0)
}
fn da_base(base: f64) -> Self {
$nome($da_base(base))
}
}
impl std::fmt::Display for $nome {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:.4} {}", self.0, $simbolo)
}
}
};
}
// Temperatura — base: Kelvin
unidade!(Celsius, "°C", |c| c + 273.15, |k| k - 273.15);
unidade!(Fahrenheit, "°F", |f| (f + 459.67) * 5.0/9.0, |k| k * 9.0/5.0 - 459.67);
unidade!(Kelvin, "K", |k| k, |k| k);
// Distância — base: metro
unidade!(Metro, "m", |m| m, |m| m);
unidade!(Quilometro, "km", |k| k * 1000.0, |m| m / 1000.0);
unidade!(Milha, "mi", |mi| mi * 1609.344, |m| m / 1609.344);
fn main() {
// Temperaturas
let fervura = Celsius::novo(100.0);
let kelvin = Kelvin::da_base(fervura.para_base());
let fahrenheit = Fahrenheit::da_base(fervura.para_base());
println!("Ponto de fervura da água:");
println!(" {fervura}");
println!(" {kelvin}");
println!(" {fahrenheit}");
// Distâncias
let maratona = Quilometro::novo(42.195);
let em_metros = Metro::da_base(maratona.para_base());
let em_milhas = Milha::da_base(maratona.para_base());
println!("
Maratona:");
println!(" {maratona}");
println!(" {em_metros}");
println!(" {em_milhas}");
}
Saída:
Ponto de fervura da água:
100.0000 °C
373.1500 K
212.0000 °F
Maratona:
42.1950 km
42195.0000 m
26.2188 mi
A macro unidade! gerou cinco structs completas com implementações de Display, métodos de conversão e traits derivados — com uma única linha cada.
Macros procedurais — uma introdução
Macros procedurais são mais poderosas mas também mais complexas. Elas precisam estar em uma crate separada do tipo proc-macro. Vamos ver como usar as já existentes, que é o que você fará na maioria dos projetos.
derive macros — as mais usadas
Já usamos #[derive(...)] extensivamente. Elas geram implementações automáticas:
// Estas macros derive são da biblioteca padrão:
#[derive(
Debug, // implementa fmt::Debug
Clone, // implementa Clone
Copy, // implementa Copy (apenas se todos campos implementam)
PartialEq, // implementa == e !=
Eq, // igualdade total (requer PartialEq)
PartialOrd, // implementa <, >, <=, >=
Ord, // ordenação total
Hash, // permite uso como chave em HashMap
Default, // implementa Default::default()
)]
struct Ponto {
x: i32,
y: i32,
}
fn main() {
let p1 = Ponto { x: 1, y: 2 };
let p2 = p1.clone();
let p3 = Ponto::default(); // { x: 0, y: 0 }
println!("{:?}", p1);
println!("p1 == p2: {}", p1 == p2);
println!("p1 > p3: {}", p1 > p3);
println!("Default: {:?}", p3);
}
serde — serialização com macros
A crate serde é um dos exemplos mais impressionantes de macros procedurais em ação:
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
struct Usuario {
id: u32,
nome: String,
email: String,
#[serde(default)]
ativo: bool,
#[serde(skip_serializing_if = "Option::is_none")]
telefone: Option<String>,
}
fn main() {
let usuario = Usuario {
id: 1,
nome: String::from("Ana Silva"),
email: String::from("ana@exemplo.com"),
ativo: true,
telefone: Some(String::from("+55 11 99999-0000")),
};
// Serialização: Rust → JSON
let json = serde_json::to_string_pretty(&usuario).unwrap();
println!("JSON:
{json}");
// Desserialização: JSON → Rust
let json_entrada = r#"
{
"id": 2,
"nome": "Carlos",
"email": "carlos@exemplo.com",
"ativo": false
}
"#;
let usuario2: Usuario = serde_json::from_str(json_entrada).unwrap();
println!("
Desserializado: {:?}", usuario2);
}
Saída:
JSON:
{
"id": 1,
"nome": "Ana Silva",
"email": "ana@exemplo.com",
"ativo": true,
"telefone": "+55 11 99999-0000"
}
Desserializado: Usuario { id: 2, nome: "Carlos", email: "carlos@exemplo.com",
ativo: false, telefone: None }
Tudo isso gerado por #[derive(Serialize, Deserialize)] — sem uma linha de código de serialização escrito manualmente.
Um programa completo: framework de testes próprio
Vamos usar macros para criar um mini framework de testes com relatório colorido:
macro_rules! suite {
($nome:expr, { $($corpo:tt)* }) => {
println!("
╔══ Suite: {} ══╗", $nome);
$($corpo)*
println!("╚{}╝
", "═".repeat($nome.len() + 12));
};
}
macro_rules! teste {
($nome:expr, $corpo:block) => {
let resultado = std::panic::catch_unwind(|| $corpo);
match resultado {
Ok(_) => println!(" ✓ {}", $nome),
Err(_) => println!(" ✗ {} [FALHOU]", $nome),
}
};
}
macro_rules! verificar {
($cond:expr) => {
if !($cond) {
panic!("Verificação falhou: {}", stringify!($cond));
}
};
($cond:expr, $msg:expr) => {
if !($cond) {
panic!("Verificação falhou: {} — {}", stringify!($cond), $msg);
}
};
}
macro_rules! verificar_eq {
($esq:expr, $dir:expr) => {
{
let esq = $esq;
let dir = $dir;
if esq != dir {
panic!(
"Valores diferentes:
esquerda: {:?}
direita: {:?}",
esq, dir
);
}
}
};
}
// Código a ser testado
fn fatorial(n: u64) -> u64 {
match n {
0 | 1 => 1,
_ => n * fatorial(n - 1),
}
}
fn e_palindromo(s: &str) -> bool {
let chars: Vec<char> = s.chars().collect();
let n = chars.len();
(0..n / 2).all(|i| chars[i] == chars[n - 1 - i])
}
fn celsius_para_fahrenheit(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
fn main() {
suite!("Fatorial", {
teste!("fatorial de 0 é 1", {
verificar_eq!(fatorial(0), 1);
});
teste!("fatorial de 1 é 1", {
verificar_eq!(fatorial(1), 1);
});
teste!("fatorial de 5 é 120", {
verificar_eq!(fatorial(5), 120);
});
teste!("fatorial de 10 é 3628800", {
verificar_eq!(fatorial(10), 3_628_800);
});
teste!("fatorial é crescente", {
verificar!(fatorial(5) < fatorial(6), "5! deve ser menor que 6!");
});
});
suite!("Palíndromo", {
teste!("'radar' é palíndromo", {
verificar!(e_palindromo("radar"));
});
teste!("'rust' não é palíndromo", {
verificar!(!e_palindromo("rust"));
});
teste!("string vazia é palíndromo", {
verificar!(e_palindromo(""));
});
teste!("'a' é palíndromo", {
verificar!(e_palindromo("a"));
});
teste!("'abba' é palíndromo", {
verificar!(e_palindromo("abba"));
});
});
suite!("Temperatura", {
teste!("0°C = 32°F", {
verificar_eq!(celsius_para_fahrenheit(0.0), 32.0);
});
teste!("100°C = 212°F", {
verificar_eq!(celsius_para_fahrenheit(100.0), 212.0);
});
teste!("-40°C = -40°F", {
verificar_eq!(celsius_para_fahrenheit(-40.0), -40.0);
});
teste!("37°C ≈ 98.6°F", {
let resultado = celsius_para_fahrenheit(37.0);
let esperado = 98.6f64;
verificar!(
(resultado - esperado).abs() < 0.01,
"diferença maior que 0.01"
);
});
});
}
Saída:
╔══ Suite: Fatorial ══╗
✓ fatorial de 0 é 1
✓ fatorial de 1 é 1
✓ fatorial de 5 é 120
✓ fatorial de 10 é 3628800
✓ fatorial é crescente
╚═══════════════════════╝
╔══ Suite: Palíndromo ══╗
✓ 'radar' é palíndromo
✓ 'rust' não é palíndromo
✓ string vazia é palíndromo
✓ 'a' é palíndromo
✓ 'abba' é palíndromo
╚════════════════════════╝
╔══ Suite: Temperatura ══╗
✓ 0°C = 32°F
✓ 100°C = 212°F
✓ -40°C = -40°F
✓ 37°C ≈ 98.6°F
╚═════════════════════════╝
Depurando macros
Quando uma macro se comporta de forma inesperada, o compilador pode expandir o código gerado. Use a flag +nightly e cargo expand (crate cargo-expand):
cargo install cargo-expand
cargo expand
Isso mostra o código Rust real gerado pelas macros — inestimável para debug.
Outra técnica: comece simples. Macros complexas devem ser construídas incrementalmente — adicione um padrão de cada vez e teste.
Quando usar macros vs funções
Macros são poderosas mas têm custos: são mais difíceis de escrever, ler e depurar do que funções. Use macros quando:
Você precisa de número variável de argumentos — funções não podem.
Você quer geração de código em tempo de compilação — structs, implementações de traits, código repetitivo.
Você está criando uma DSL — uma sintaxe especializada que simplifica um domínio.
Use funções para todo o resto — são mais simples, têm erros mais claros, e se beneficiam de todas as ferramentas de análise estática.
A regra de ouro: se você pode resolver com uma função genérica, use uma função. Macros são o último recurso expressivo, não o primeiro.
Fontes e leituras recomendadas
- The Rust Programming Language, Cap. 19.5 — Macros — https://doc.rust-lang.org/book/ch19-06-macros.html
- Rust by Example — Macros — https://doc.rust-lang.org/rust-by-example/macros.html
- "The Little Book of Rust Macros" — guia completo sobre macro_rules! — https://veykril.github.io/tlborm/
cargo-expand— ferramenta para expandir macros — https://github.com/dtolnay/cargo-expandserdedocumentation — o exemplo mais impressionante de macros procedurais — https://serde.rs- "Procedural Macros Workshop" — David Tolnay — tutorial prático de macros procedurais — https://github.com/dtolnay/proc-macro-workshop
- Rustlings — não tem seção de macros, mas os exemplos anteriores ajudam a consolidar — https://github.com/rust-lang/rustlings