cargo-expand: Debug de Macros Rust | Rust Brasil

Use cargo-expand para ver macros Rust expandidas: derive, serde, clap, proc-macros, filtros por item e fluxo de debug no dia a dia e na CI de bibliotecas.

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

EtapaComando ou configuraçãoObjetivo
Instalarcargo install cargo-expand --lockedDisponibilizar o subcomando
Dump completocargo expandVer a expansão do crate atual
Filtrar módulocargo expand caminho::do::moduloReduzir ruído
Bibliotecacargo expand --libFocar no target lib
Bináriocargo expand --bin nomeExpandir um binário específico
Testecargo expand --test nome_do_testeInspecionar macros em testes
Diff localsalvar saída e comparar versõesDetectar 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:

  1. o derive de Serde gera um Visitor que falha em um caso específico;
  2. o Clap produz flags e help inesperados;
  3. uma proc-macro emite unsafe ou imports que você não previa;
  4. o código compila no seu laptop e quebra no consumidor com features diferentes;
  5. a monomorphization de um derive explode o tempo de build ou o tamanho do binário;
  6. 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 impl esperado;
  • 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:

  1. escolher o target (--lib, --bin, --test, --example);
  2. escolher o caminho do módulo ou item;
  3. 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á mod visí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 (nomeCompleto vs nome_completo);
  • campos com default, skip_serializing_if e flatten aparecem como esperado;
  • enums com tag/content geraram 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:

  1. escreva um crate de exemplo mínimo que usa a macro;
  2. expanda esse exemplo;
  3. compare a saída com o contrato público documentado;
  4. adicione testes de snapshot ou asserts sobre a expansão quando a estabilidade da API gerada importar;
  5. 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

FerramentaPergunta que respondeQuando usar
rust-analyzero que esta macro gera aqui no cursor?exploração rápida no editor
cargo-expandqual é o código expandido completo deste target/módulo?debug profundo, diff, review
cargo-llvm-linesquanto código monomorphizado isso produz?custo de compilação
cargo-bloato que inchou o binário?tamanho de release
Criteriona mudança ficou mais rápida?performance runtime

Sequência pragmática:

  1. falhou a compilação ou o comportamento ficou estranho → cargo expand no módulo suspeito;
  2. a expansão revela código repetitivo enorme → meça com cargo-llvm-lines;
  3. o release ficou pesado → inspecione com cargo-bloat;
  4. 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

  1. Expandir o crate inteiro sem filtro. Gera ruído, esconde o item útil e consome tempo.
  2. Misturar target. Expandir --lib quando o bug está no --bin ou no --test.
  3. Ignorar features. A expansão sem a feature ofensora “prova” o que não existe no bug real.
  4. Tratar nightly como toolchain de produção. Use nightly para inspecionar; mantenha stable/MSRV para build e release (cargo-msrv).
  5. Ler expansão como garantia de performance. Código gerado verboso pode ser otimizado pelo LLVM — meça.
  6. 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.
  7. Expor segredos. Dumps podem incluir paths locais, nomes internos ou módulos privados; revise antes de colar em issues públicas.
  8. Confundir com cargo expand de 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-expand instalado 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 -p explicitamente
  • 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.