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ção | Ferramenta indicada | Por quê |
|---|---|---|
| Check rápido no dia a dia ou em pull request | cargo-machete | Roda em segundos, não exige nightly nem build completo |
| Confirmar um caso duvidoso antes de remover | cargo-udeps | Usa o compilador, reduzindo palpite sobre uso real |
| Workspace grande com muitas crates | As duas | Machete varre tudo rápido; udeps aprofunda no pacote suspeito |
| Projeto preso a uma toolchain estável na CI | cargo-machete | udeps precisa de nightly instalada no job |
| Auditoria antes de um release importante | As duas | Custo 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
usede 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:
| Machete | udeps | Leitura provável |
|---|---|---|
| Aponta | Aponta | Candidato forte para remoção |
| Aponta | Não aponta | Uso invisível ao texto: macro, build script, reexport, cfg |
| Não aponta | Aponta | Referência existe no código, mas não entra no build atual (feature ou target desativado) |
| Não aponta | Não aponta | Dependê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:
- rode
cargo machetee anote os candidatos; - confirme com
cargo +nightly udeps --all-targets; - verifique se a crate aparece em
build.rs, macros, reexports ou blocoscfg; - remova uma dependência por vez;
- rode
cargo check --all-targetsecargo test(ou cargo-nextest); - compile também com
--all-featurese--no-default-features; - se o projeto suporta outros sistemas, compile para os targets relevantes — veja cargo-zigbuild para cross-compilation;
- rode Clippy e
cargo fmt --check; - confirme o efeito no
Cargo.locke no binário; - 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:
| Pergunta | Ferramenta |
|---|---|
| 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-udepsem 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 installem todo job; - nunca adicione um
ignoreglobal 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.