O cargo-expand é a ferramenta certa quando uma macro Rust gera código que você precisa enxergar de verdade — derive de Serde, parser de Clap, erro customizado com thiserror, ou a proc-macro que sua biblioteca publica. Instale com cargo install cargo-expand --locked, rode cargo expand na raiz do crate e trate o dump como o primeiro passo para entender o que o compilador está compilando. Em bibliotecas, SDKs e crates com muitos derives, isso costuma encurtar debugs que o editor sozinho não fecha.
Este guia mostra o que o cargo-expand resolve, como filtrar por módulo e item, como usá-lo com Serde, Clap, macros e proc-macros, como combinar a inspeção com rust-analyzer, cargo-llvm-lines e cargo-hack, e quais armadilhas geram dumps enormes ou conclusões erradas.
Resposta rápida: o fluxo recomendado
| Etapa | Comando ou configuração | Objetivo |
|---|---|---|
| Instalar | cargo install cargo-expand --locked | Disponibilizar o subcomando |
| Dump completo | cargo expand | Ver a expansão do crate atual |
| Filtrar módulo | cargo expand caminho::do::modulo | Reduzir ruído |
| Biblioteca | cargo expand --lib | Focar no target lib |
| Binário | cargo expand --bin nome | Expandir um binário específico |
| Teste | cargo expand --test nome_do_teste | Inspecionar macros em testes |
| Diff local | salvar saída e comparar versões | Detectar regressão de geração |
Antes de automatizar dumps em CI crítica, confira cargo expand --help e a documentação atual. A ferramenta e as flags do compilador evoluem; fixe a versão se a saída for usada em revisão formal.
Por que macros Rust escondem o código real
Macros são o mecanismo que permite APIs ergonômicas em Rust:
#[derive(Serialize, Deserialize)]em modelos de API;#[derive(Parser)]em CLIs com Clap;#[derive(Error)]com thiserror;- macros declarativas de DSL interna;
- proc-macros que geram builders, clients e bindings.
O ganho de ergonomia tem um custo cognitivo. Quando o erro aponta para uma linha gerada, a mensagem pode citar tipos, traits ou lifetimes que não existem no arquivo que você editou. O rust-analyzer ajuda a expandir no editor, mas dumps longos, diffs entre versões e revisão assíncrona pedem texto versionável.
Cenários típicos em que a expansão paga o tempo investido:
- o derive de Serde gera um
Visitorque falha em um caso específico; - o Clap produz flags e help inesperados;
- uma proc-macro emite
unsafeou imports que você não previa; - o código compila no seu laptop e quebra no consumidor com features diferentes;
- a monomorphization de um derive explode o tempo de build ou o tamanho do binário;
- o erro menciona um trait bound gerado que você precisa satisfazer manualmente.
O cargo-expand transforma “acho que a macro fez X” em “aqui está o código gerado”.
O que é cargo-expand
O cargo-expand é um subcomando Cargo que orquestra a expansão de macros e imprime o resultado em Rust pretty-printed. Em termos práticos, ele aproxima o fluxo de “mostrar o código depois das macros” sem exigir que você memorize a invocação bruta do rustc a cada investigação.
Ele não é um formatador, não substitui testes e não prova corretude. É uma lente. Você usa a lente para:
- confirmar se a macro gerou o
implesperado; - localizar o trecho responsável por um erro;
- comparar duas versões da mesma macro;
- ensinar ou documentar o efeito de um derive;
- auditar o volume de código gerado antes de medir com outras ferramentas.
Para o panorama mais amplo de macros declarativas e procedurais, combine este guia com o artigo Macros em Rust e a visão geral de ferramentas essenciais do Cargo.
Instalando e primeiros comandos
cargo install cargo-expand --locked
rustup component add rustfmt --toolchain nightly
cargo expand --help
A expansão completa normalmente depende de nightly. Um padrão saudável em times brasileiros que publicam crates é:
- desenvolver e testar em stable;
- inspecionar expansão em nightly pontual;
- documentar no README qual toolchain usar para
cargo expand.
Fluxo mínimo em um crate de biblioteca:
cd meu-crate
cargo expand --lib
Fluxo mínimo em um binário:
cargo expand --bin app
Se a saída for gigante, não role o terminal sem filtro. Reduza o escopo:
cargo expand models
cargo expand models::usuario
cargo expand --lib cli::args
Salve dumps relevantes quando for comparar:
cargo expand --lib models > /tmp/models-before.rs
# altere a macro, a feature ou a versão do derive
cargo expand --lib models > /tmp/models-after.rs
diff -u /tmp/models-before.rs /tmp/models-after.rs | less
Filtrando ruído: módulo, item e target certos
A maior falha de uso do cargo-expand é pedir o dump do mundo inteiro. Workspaces com Axum, SQLx, Serde e dezenas de módulos geram milhares de linhas. Prefira:
- escolher o target (
--lib,--bin,--test,--example); - escolher o caminho do módulo ou item;
- só então ampliar o escopo se o trecho não aparecer.
Exemplos úteis:
# só a lib
cargo expand --lib
# módulo de domínio
cargo expand --lib domain::pedido
# argumentos de CLI
cargo expand --bin api cli
# macros usadas em teste de integração
cargo expand --test http_contrato
Se o item não aparecer, confirme:
- o módulo está
modvisível no target escolhido; - a feature que habilita o módulo está ativa;
- você não está expandindo o pacote errado em um workspace.
Em workspaces, seja explícito:
cargo expand -p minha_lib --lib
cargo expand -p minha_cli --bin minha_cli
Esse cuidado combina bem com Cargo workspaces e com matrizes de features via cargo-hack.
Serde, Clap e derives do dia a dia
Serde
Modelos com Serialize/Deserialize são o caso de uso mais comum. Quando um payload JSON falha de forma opaca, expanda o módulo do modelo e procure o Visitor, os field names e os #[serde(...)] efetivos.
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Usuario {
pub id: uuid::Uuid,
pub nome_completo: String,
#[serde(default)]
pub ativo: bool,
}
cargo expand --lib models::usuario
Checklist rápido na saída:
- os nomes serializados batem com a API (
nomeCompletovsnome_completo); - campos com
default,skip_serializing_ifeflattenaparecem como esperado; - enums com
tag/contentgeraram as variantes corretas; - tipos newtype não introduziram wrapper surpresa.
Para o fluxo de API HTTP, conecte a inspeção com Serde, Axum e o guia de validação de dados.
Clap
CLIs com #[derive(Parser)] escondem parsing, help e env vars no código gerado. Se uma flag não aparece, se o default está errado ou se o help ficou inconsistente, expanda o módulo de argumentos.
cargo expand --bin minha_cli cli::args
Verifique na expansão:
- nomes longos/curtos das flags;
- valores default e
env; - subcomandos e enums de comando;
- obrigatoriedade de argumentos posicionais.
Combine com a página de Clap e com o hábito de testar a CLI de ponta a ponta — expansão explica o parser; testes confirmam o comportamento.
thiserror e anyhow
Derives de erro geram Display, From e source chaining. Quando a mensagem formatada ou a conversão entre erros não bate, expanda o módulo de erros antes de culpar o runtime.
cargo expand --lib error
Depois valide o comportamento com testes e, se necessário, revise o guia de tratamento de erros com thiserror e anyhow.
Proc-macros: o que inspecionar de verdade
Se você mantém uma proc-macro, o cargo-expand é parte do ciclo de desenvolvimento, não um luxo. Fluxo recomendado:
- escreva um crate de exemplo mínimo que usa a macro;
- expanda esse exemplo;
- compare a saída com o contrato público documentado;
- adicione testes de snapshot ou asserts sobre a expansão quando a estabilidade da API gerada importar;
- rode a matriz de features com cargo-hack para garantir que a macro não depende de um conjunto mágico de flags.
Pontos de atenção na saída gerada:
- imports absolutos vs relativos;
- higiene de identificadores;
- emissão acidental de
unsafe; - bounds de trait implícitos demais;
- código que só compila com features internas do workspace.
Não transforme o dump em único teste. Expansão mostra estrutura; testes de integração mostram comportamento.
Relação com rust-analyzer, llvm-lines e bloat
| Ferramenta | Pergunta que responde | Quando usar |
|---|---|---|
| rust-analyzer | o que esta macro gera aqui no cursor? | exploração rápida no editor |
| cargo-expand | qual é o código expandido completo deste target/módulo? | debug profundo, diff, review |
| cargo-llvm-lines | quanto código monomorphizado isso produz? | custo de compilação |
| cargo-bloat | o que inchou o binário? | tamanho de release |
| Criterion | a mudança ficou mais rápida? | performance runtime |
Sequência pragmática:
- falhou a compilação ou o comportamento ficou estranho →
cargo expandno módulo suspeito; - a expansão revela código repetitivo enorme → meça com cargo-llvm-lines;
- o release ficou pesado → inspecione com cargo-bloat;
- a hipótese de performance for clara → valide com Criterion ou com o guia de profiling em produção.
A expansão sozinha não autoriza micro-otimização. Ela só evita otimizar no escuro.
Features, workspaces e dumps reproduzíveis
Macros frequentemente dependem de features. Expanda com as mesmas flags que o bug reproduz:
cargo expand --lib --features serde-full
cargo expand --lib --no-default-features --features alloc
Em workspaces, declare o pacote:
cargo expand -p sdk_core --lib types
cargo expand -p sdk_cli --bin sdk --features unstable
Boas práticas para dumps compartilhados em pull request:
- fixe a versão do
cargo-expand; - documente toolchain nightly usada;
- filtre o módulo mínimo que demonstra o problema;
- evite colar 5.000 linhas no GitHub sem contexto;
- se o dump for artefato de CI, publique como artifact, não como comentário gigante.
Se a macro só quebra em uma combinação rara de features, a matriz do cargo-hack encontra o cenário; o cargo-expand explica o cenário.
Armadilhas comuns
- Expandir o crate inteiro sem filtro. Gera ruído, esconde o item útil e consome tempo.
- Misturar target. Expandir
--libquando o bug está no--binou no--test. - Ignorar features. A expansão sem a feature ofensora “prova” o que não existe no bug real.
- Tratar nightly como toolchain de produção. Use nightly para inspecionar; mantenha stable/MSRV para build e release (cargo-msrv).
- Ler expansão como garantia de performance. Código gerado verboso pode ser otimizado pelo LLVM — meça.
- Diffs instáveis. Ordem de itens e formatação podem mudar entre versões; compare com critério e, se possível, mesma versão da ferramenta.
- Expor segredos. Dumps podem incluir paths locais, nomes internos ou módulos privados; revise antes de colar em issues públicas.
- Confundir com
cargo expandde dependências transitivas. Comece pelo seu código; só aprofunde em crates externos quando a evidência apontar para eles — e aí a conversa encontra cargo-vet e cargo-audit.
Checklist de adoção
-
cargo-expandinstalado com--locked - nightly disponível apenas para inspeção
- comando mínimo documentado no README do crate (
cargo expand --lib modulo) - exemplos de Serde/Clap/erro cobertos por pelo menos um dump local na investigação
- features relevantes reproduzidas na expansão
- workspace usa
-pexplicitamente - dumps grandes vão para artifact/diff, não para comentário cego
- hipóteses de custo seguem para llvm-lines/bloat/benchmark
- proc-macros críticas têm exemplo mínimo expansível
cargo-expand e carreira Rust
Saber expandir macros é sinal de maturidade em bibliotecas e plataformas. Em entrevistas e portfólios brasileiros de Rust, isso aparece quando você:
- explica um bug de Serde/Clap mostrando o código gerado;
- documenta uma proc-macro com exemplo antes/depois;
- reduz tempo de build ao identificar derive excessivo;
- colabora em crates open source com reproduções mínimas.
Para montar trilha prática, combine este guia com projetos práticos em Rust, entrevista backend, vagas Rust e o hub de carreira Rust 2026. Empresas que publicam SDKs, CLIs e bibliotecas internas valorizam quem debuga o gerado, não só quem consome o derive.
Perguntas frequentes
O que é cargo-expand?
cargo-expand é uma ferramenta do ecossistema Cargo que mostra o código Rust gerado depois da expansão de macros. Ela ajuda a inspecionar derives, macros declarativas e procedurais, filtrar por módulo ou item e entender o que o compilador realmente vê antes de analisar erros obscuros.
Quando vale a pena usar cargo-expand?
Vale a pena ao debugar derives de Serde, Clap, thiserror e similares, ao escrever ou manter proc-macros, ao investigar erros que apontam para código gerado e ao revisar o custo de abstrações baseadas em macros. Em código sem macros, cargo check e rust-analyzer costumam ser suficientes.
cargo-expand substitui rust-analyzer ou cargo check?
Não. rust-analyzer oferece navegação e expansão parcial no editor; cargo check valida a compilação; cargo-expand materializa o código expandido em texto para inspeção completa, diff e compartilhamento em code review. As três ferramentas se complementam.
Preciso de nightly para usar cargo-expand?
Em geral, sim: a expansão completa depende de recursos do compilador disponíveis no toolchain nightly. Na prática, você mantém o projeto em stable e usa nightly apenas para inspecionar a expansão. Confirme a documentação atual da ferramenta e fixe a versão na CI se for automatizar dumps.
cargo-expand serve para performance?
Indiretamente. Expandir macros não otimiza o binário, mas revela código gerado repetitivo e padrões que depois você pode medir com cargo-llvm-lines, cargo-bloat ou Criterion. Use a expansão para entender o que foi gerado; use benchmarks para decidir o que mudar.
Conclusão
cargo-expand não substitui testes, typecheck ou bom design de macros. Ele remove a opacidade. Quando o erro mora no código gerado, a saída expandida é o mapa.
Comece pequeno: instale a ferramenta, expanda um módulo com Serde ou Clap, filtre até o item útil e documente o comando no README. Se a macro for sua, transforme o exemplo mínimo em parte do fluxo de desenvolvimento. Se o problema for custo de build ou tamanho, siga para llvm-lines e bloat com evidência, não com achismo.
Para continuar no cluster de tooling Rust, leia também cargo-hack, cargo-msrv, macros em Rust, Serde e a visão geral de Cargo. Se estiver buscando aplicar isso em produto, explore as vagas e as empresas que usam Rust no Brasil.