cargo-machete vs cargo-udeps: Deps Não Usadas | Rust Brasil

Encontre dependências não usadas em Rust com cargo-machete e cargo-udeps. Guia com instalação, workspaces, falsos positivos, CI e impacto no build na prática.

Para encontrar dependências não usadas em um projeto Rust, rode cargo machete para uma varredura rápida e confirme os achados com cargo +nightly udeps antes de apagar qualquer linha do Cargo.toml. As duas ferramentas respondem à mesma pergunta — “esta crate ainda serve para alguma coisa?” — usando estratégias diferentes: uma analisa o código-fonte, a outra se apoia na compilação real.

Essa limpeza raramente aparece em roadmap, mas paga bem. Cada dependência esquecida continua sendo resolvida pelo Cargo, baixada na CI, compilada, cacheada e auditada. Ela aumenta o tempo de build, o tamanho do Cargo.lock, a superfície de supply chain e o custo de revisar advisories que não afetam o produto em nada.

Este guia mostra como instalar e usar cargo-machete e cargo-udeps, como interpretar cada relatório, como lidar com workspaces e features, quais são os falsos positivos clássicos e como colocar a verificação na CI sem transformá-la em fonte de ruído.

Resposta rápida: qual ferramenta usar

SituaçãoFerramenta indicadaPor quê
Check rápido no dia a dia ou em pull requestcargo-macheteRoda em segundos, não exige nightly nem build completo
Confirmar um caso duvidoso antes de removercargo-udepsUsa o compilador, reduzindo palpite sobre uso real
Workspace grande com muitas cratesAs duasMachete varre tudo rápido; udeps aprofunda no pacote suspeito
Projeto preso a uma toolchain estável na CIcargo-macheteudeps precisa de nightly instalada no job
Auditoria antes de um release importanteAs duasCusto maior é aceitável quando o objetivo é enxugar o artefato

Nenhuma das duas decide sozinha. Elas produzem candidatos; a remoção final é uma decisão de engenharia validada por compilação, testes e features.

Por que dependências mortas se acumulam

Um Cargo.toml cresce por caminhos previsíveis:

  • um protótipo virou produção e ninguém revisitou as crates iniciais;
  • uma biblioteca de HTTP foi trocada por outra, mas a antiga ficou declarada;
  • um utilitário de datas foi substituído por std;
  • um teste temporário puxou uma dependência que continuou no [dependencies] em vez de [dev-dependencies];
  • uma feature experimental foi revertida no código, não no manifesto;
  • um refactor removeu o último use de uma crate sem tocar no manifesto.

O compilador não reclama. Rust avisa sobre imports não usados dentro de um arquivo, mas não sobre uma crate declarada e nunca referenciada. É exatamente essa lacuna que cargo-machete e cargo-udeps preenchem.

O efeito colateral aparece devagar: builds mais lentos, cache maior, dependências transitivas desnecessárias e relatórios de segurança apontando crates que o produto sequer usa. Para o contexto mais amplo dessa disciplina, veja nosso guia de segurança e supply chain com cargo-deny e SBOM.

cargo-machete: a varredura rápida

O cargo-machete percorre o Cargo.toml e o código-fonte procurando referências a cada dependência declarada. Como não depende de compilação completa, ele é rápido o suficiente para rodar em qualquer pull request.

Instalação e primeira execução

cargo install cargo-machete --locked
cargo machete --version

Na raiz do projeto:

cargo machete

A saída lista, por pacote, as dependências que a ferramenta não conseguiu associar a nenhum uso no código. Um exemplo ilustrativo:

cargo-machete found the following unused dependencies in /projeto:
minha-api -- minha-api/Cargo.toml:
	chrono
	once_cell

Isso significa: “o manifesto declara chrono e once_cell, mas eu não encontrei uso delas no código-fonte deste pacote”. É uma hipótese forte, não um veredito.

Consultando um workspace inteiro

Em um monorepo, a execução na raiz percorre os membros do workspace e agrupa o resultado por pacote. Isso combina bem com a organização descrita no guia de workspaces Cargo e monorepos: você descobre rapidamente qual crate acumulou manifesto além do necessário.

Um fluxo prático:

cargo machete > /tmp/machete-antes.txt
# ... remova dependências, ajuste features ...
cargo machete > /tmp/machete-depois.txt
diff -u /tmp/machete-antes.txt /tmp/machete-depois.txt

Ignorando falsos positivos de forma explícita

Quando uma dependência é legítima mas a análise não consegue enxergar o uso — o caso típico é uma crate consumida apenas por macro, build script ou reexport — o cargo-machete permite declarar a exceção no próprio manifesto, em vez de espalhar comentários pelo repositório:

[package.metadata.cargo-machete]
ignored = ["crate-usada-por-macro"]

Essa marcação é melhor do que silenciar a ferramenta inteira. Ela documenta a decisão no lugar certo, sobrevive a trocas de time e pode ser revisada em code review. Confirme a chave exata suportada pela versão instalada com cargo machete --help.

cargo-udeps: a confirmação pelo compilador

O cargo-udeps responde à mesma pergunta por outro caminho. Em vez de procurar referências no texto, ele se apoia na compilação para observar quais dependências realmente participaram do build. Por isso ele é mais preciso — e mais caro.

Instalação e execução

rustup toolchain install nightly
cargo install cargo-udeps --locked
cargo +nightly udeps

A exigência de nightly costuma gerar hesitação, mas não implica adotar nightly no produto. Você pode manter stable como toolchain oficial e instalar nightly apenas no job de auditoria ou na máquina de quem faz a limpeza.

Variações úteis:

cargo +nightly udeps --all-targets
cargo +nightly udeps -p minha-api
cargo +nightly udeps --all-features
cargo +nightly udeps --no-default-features

O --all-targets é especialmente importante: sem ele, dependências usadas somente em testes, benchmarks ou exemplos podem parecer inúteis.

Quando o resultado das duas ferramentas diverge

Divergência é informação, não defeito. Interprete assim:

MacheteudepsLeitura provável
ApontaApontaCandidato forte para remoção
ApontaNão apontaUso invisível ao texto: macro, build script, reexport, cfg
Não apontaApontaReferência existe no código, mas não entra no build atual (feature ou target desativado)
Não apontaNão apontaDependência em uso normal

O segundo e o terceiro caso são os mais instrutivos. Eles costumam revelar que o projeto tem código morto atrás de uma feature, ou que uma crate só é exercida em um target específico.

Falsos positivos clássicos

Antes de apagar qualquer linha, confira estes cenários:

Build scripts. Uma crate usada apenas em build.rs pertence a [build-dependencies]. Se ela está no lugar errado, o relatório pode ficar confuso nos dois sentidos.

Macros e derives. Uma dependência puxada por #[derive(...)] de outra crate pode não aparecer como use explícito. É o caso mais comum de falso positivo do machete.

Reexports. Uma biblioteca que faz pub use outra_crate::Tipo; expõe a dependência como parte da API pública, mesmo sem chamar métodos dela. Removê-la é uma quebra de contrato — assunto tratado no guia de cargo-semver-checks e SemVer na CI.

Código atrás de cfg. Uma crate usada somente em Windows, somente em target_arch específico ou somente com #[cfg(feature = "...")] pode sumir do build padrão. Reveja o guia de compilação condicional com cfg e features quando o relatório parecer estranho.

Dev-dependencies. Sem --all-targets, testes de integração, benchmarks com Criterion e exemplos não entram na conta.

Features opcionais. Uma dependência opcional pode existir justamente para ser ativada por quem consome a biblioteca. Ela é “não usada” no build padrão e ainda assim essencial ao produto.

Checklist de remoção segura

Trate cada remoção como uma mudança de código, não como faxina de manifesto:

  1. rode cargo machete e anote os candidatos;
  2. confirme com cargo +nightly udeps --all-targets;
  3. verifique se a crate aparece em build.rs, macros, reexports ou blocos cfg;
  4. remova uma dependência por vez;
  5. rode cargo check --all-targets e cargo test (ou cargo-nextest);
  6. compile também com --all-features e --no-default-features;
  7. se o projeto suporta outros sistemas, compile para os targets relevantes — veja cargo-zigbuild para cross-compilation;
  8. rode Clippy e cargo fmt --check;
  9. confirme o efeito no Cargo.lock e no binário;
  10. registre no commit por que a dependência saiu.

O passo 4 parece burocrático, mas evita a situação clássica: cinco remoções juntas, build quebrado e ninguém sabe qual delas causou o problema.

Medindo o ganho real

Remover dependências é satisfatório, mas o benefício precisa ser verificado. Antes e depois da limpeza:

cargo clean
/usr/bin/time -v cargo build --release --locked

Compare também o número de crates resolvidas:

cargo tree | wc -l
cargo tree -e features | wc -l

E o artefato final, com o método descrito no guia de cargo-bloat para reduzir o tamanho do binário:

cargo bloat --release --crates

Nem toda remoção muda o binário. O linker costuma descartar código não referenciado, então uma dependência morta pode custar tempo de compilação sem custar bytes. Por isso as perguntas devem ser separadas:

PerguntaFerramenta
Esta dependência ainda é usada?cargo-machete, cargo-udeps
O build ficou mais rápido?build limpo cronometrado, cargo build --timings
O binário ficou menor?cargo-bloat
Genéricos estão gerando código demais?cargo-llvm-lines
O gargalo é link, não compilação?mold
O gargalo é recompilação repetida na CI?sccache

Se o objetivo principal é reduzir tempo de build, comece pelo diagnóstico do guia de como otimizar o tempo de compilação em Rust. Dependência morta é uma causa possível, não a única.

Integrando na CI sem gerar ruído

Um job leve com cargo-machete funciona bem como check contínuo:

name: deps

on:
  pull_request:
  workflow_dispatch:

jobs:
  unused-deps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Instalar Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Instalar cargo-machete
        run: cargo install cargo-machete --locked

      - name: Verificar dependências não usadas
        run: cargo machete

O mesmo desenho funciona em Gitea Actions quando o runner oferece actions compatíveis — lembrando que este site mantém workflows nos dois formatos e eles precisam permanecer alinhados. Para o pipeline completo, veja o guia de CI/CD em projetos Rust.

Recomendações práticas:

  • comece com o job informativo; promova a obrigatório depois que os falsos positivos estiverem documentados em [package.metadata.cargo-machete];
  • deixe cargo-udeps em um job agendado (semanal) ou manual, já que exige nightly e build completo;
  • pine a versão da ferramenta ou use cache para não pagar cargo install em todo job;
  • nunca adicione um ignore global sem justificativa — a exceção deve ser nominal e revisável.

Por que isso aparece em entrevista

Saber limpar dependências é um sinal de maturidade operacional. Em entrevistas para posições de backend, plataforma e infraestrutura, é comum pedirem que o candidato explique como reduziria o tempo de build de um serviço Rust. Responder “eu removeria dependências” é fraco; responder com um método — machete para triagem, udeps para confirmação, medição antes/depois, atenção a features e targets, exceções documentadas — mostra que a pessoa opera o projeto, não apenas escreve código.

Esse tipo de competência aparece em vagas de plataforma, ferramentas internas e times que mantêm bibliotecas compartilhadas. Acompanhe as vagas Rust e as empresas que usam Rust no Brasil para ver como esses requisitos são descritos, e o plano de estudos sênior para encaixar tooling na trilha de crescimento.

A disciplina também não é exclusiva de Rust. Times que trabalham com Go enfrentam a mesma tensão entre módulos declarados e módulos realmente usados; o portal Golang Brasil cobre o lado do ecossistema Go. A diferença é que, em Rust, o custo de uma dependência esquecida tende a ser mais visível no tempo de compilação.

Perguntas frequentes

Como encontrar dependências não usadas em Rust?

Rode cargo machete para uma varredura rápida e cargo +nightly udeps --all-targets para confirmar. Trate o resultado como lista de candidatos e valide cada remoção com build e testes.

Qual a diferença entre cargo-machete e cargo-udeps?

cargo-machete analisa manifesto e código-fonte, sendo rápido e compatível com toolchain estável. cargo-udeps se apoia na compilação e exige nightly, entregando mais precisão a um custo maior de tempo.

cargo-udeps precisa mesmo de nightly?

Sim, ele é executado como cargo +nightly udeps. Você pode instalar a toolchain nightly apenas para o job de auditoria, mantendo stable como toolchain do projeto.

Remover dependências deixa o binário menor?

Nem sempre. O linker já descarta código não referenciado, então o ganho mais frequente é em tempo de build, cache e superfície de auditoria. Confirme com cargo-bloat antes de prometer redução de tamanho.

Como lidar com falsos positivos?

Documente a exceção em [package.metadata.cargo-machete] com a lista ignored, explicando no commit por que a dependência é necessária (macro, build script, reexport ou feature opcional).

Conclusão

cargo-machete e cargo-udeps não competem: eles se complementam. O primeiro é o filtro barato que cabe em toda pull request; o segundo é a confirmação cara que vale antes de mexer no manifesto. Juntos, transformam “acho que essa crate não é mais usada” em uma decisão apoiada por evidência.

O fluxo recomendado é simples: varra com machete, confirme com udeps, remova uma dependência por vez, valide com todas as features e targets relevantes e meça o efeito no build e no binário. Documente as exceções onde elas fazem sentido — no Cargo.toml — e deixe a CI lembrar o time quando o manifesto começar a crescer sozinho de novo.

Para continuar a limpeza, combine este guia com cargo-bloat, cargo-llvm-lines e as ferramentas essenciais do Cargo. Cada uma responde a uma pergunta diferente sobre o mesmo projeto — e é essa separação que evita otimizar o número errado.