Ferramentas de Qualidade Integradas do Rust
Toda toolchain do Rust vem com duas ferramentas que a maioria dos outros ecossistemas adiciona como complementos de terceiros: rustfmt para formatação e clippy para linting.
Ambas se baseiam em um sistema de lint que é, em si, parte do compilador, rustc.
Juntas, elas formam um pipeline em camadas que roda em cada build, cada salvamento e cada job de CI sem que uma equipe precise escolher, instalar ou manter uma stack de linter e formatador separada.
Esta página é a âncora conceitual para o restante da seção de linting e formatação: ela explica o que cada camada realmente verifica, como elas se comunicam e por que o sistema de níveis de lint é o mecanismo que torna tudo configurável sem se tornar arbitrário.
Resumo
- Rust agrupa um formatador determinístico (
rustfmt), um linter adjacente ao compilador (clippy) e um sistema de severidade de lint ajustável (allow/warn/deny/forbid) como partes de primeira classe da toolchain, em vez de ferramentas comunitárias opcionais. - Por que Importa: Argumentos de estilo e uma grande classe de código "tecnicamente correto, mas suspeito" são resolvidos automaticamente em vez de consumir tempo do revisor em cada pull request.
- Conceitos Chave: rustfmt, clippy, lints do rustc, níveis de lint, atributos de lint, a tabela
[lints]do manifest. - Quando Usar: Todo projeto Rust se beneficia de
cargo fmtecargo clippyem CI desde o primeiro dia, e o ajuste de nível de lint se torna relevante assim que uma base de código precisa de garantias mais rigorosas, como proibirunsafeou negarunwrapfora dos testes. - Limitações / Trade-offs: Formatação e linting capturam problemas de estilo e idiomáticos, não bugs de lógica, e uma política de lint excessivamente rigorosa pode desacelerar os contribuidores ou produzir falsos positivos ruidosos em código gerado ou com muita FFI.
- Tópicos Relacionados: diagnósticos do compilador, análise estática, gating de integração contínua, tooling de editor via rust-analyzer.
Fundamentos
rustfmt é um formatador de código-fonte.
Ele pega Rust sintaticamente válido e reescreve seus espaços em branco, quebras de linha e ordenação de imports em um layout canônico definido pelas próprias regras da ferramenta.
Ao contrário de formatadores em algumas outras linguagens, rustfmt expõe intencionalmente apenas uma superfície de configuração pequena e majoritariamente cosmética através de rustfmt.toml (largura da linha, agrupamento de imports, detalhes do estilo de chaves), porque o objetivo inteiro é remover o gosto por desenvolvedor da equação, em vez de acomodá-lo.
Dois engenheiros executando cargo fmt no mesmo arquivo, na mesma toolchain, sempre produzem saída byte-idêntica.
Esse determinismo é o mecanismo, não um efeito colateral: é o que permite que uma equipe pare de debater tabs versus espaços ou onde uma chave de fechamento vai.
clippy é uma ferramenta separada, mas intimamente integrada: uma grande coleção de passes adicionais do compilador, invocada com cargo clippy em vez de cargo build ou cargo check.
Onde rustc apenas recusa código que é inseguro, ambíguo ou um erro de compilação, clippy procura por código que compila bem, mas é provavelmente um erro, é não idiomático ou é mensuravelmente mais lento que uma alternativa equivalente.
Um exemplo clássico é clonar um iterador de tipos Copy com .cloned() em vez do mais barato .copied(); rustc não tem opinião sobre isso, mas clippy tem.
Clippy organiza seus vários centenas de lints em grupos nomeados como correctness, suspicious, style, perf e pedantic, variando de "quase certamente um bug" a "nitpick estilístico que vale a pena saber".
Por baixo de ambas as ferramentas está o sistema de lint do Rust, que é um recurso do próprio rustc, não do clippy.
Todo lint, seja ele vindo do compilador ou do clippy, tem um nível: allow, warn, deny ou forbid.
Este é o dial referenciado ao longo desta seção: o mesmo lint pode ficar silencioso em um crate e ser uma falha de build rígida em outro, sem alterar uma única linha da implementação do lint.
Mecânicas & Interações
As três camadas rodam em uma relação específica, e entender essa relação explica muito do comportamento que, de outra forma, pareceria arbitrário.
rustc compila o código e avalia seus próprios lints embutidos primeiro; se o código não compilar, nem rustfmt nem clippy terão uma chance significativa de rodar, porque ambas as ferramentas precisam de um programa analisável, e para clippy, com tipos verificados, para analisar.
clippy então roda como um segundo passe, reutilizando internamente a análise e inferência de tipos do rustc, que é por isso que cargo clippy é mais lento que cargo check, mas mais rápido do que uma reimplementação do zero seria.
rustfmt opera independentemente de ambos, trabalhando no stream de tokens em vez do AST com tipos verificados, que é por isso que ele pode formatar código que ainda nem compila.
código-fonte
|
v
[ rustc: parse, typecheck, lints próprios ] --falha--> erro de compilação (fmt/clippy bloqueados)
|
v
[ clippy: passes de lint adicionais no AST tipado ] --deny--> erro de lint (build falha)
|
v
binário
Os níveis de lint são resolvidos de vários lugares, e Rust define uma precedência clara: atributos inline como #[allow(clippy::too_many_arguments)] em um item específico são os mais locais e específicos, atributos internos #![...] na raiz do módulo ou crate se aplicam mais amplamente, e a tabela [lints] em Cargo.toml (estável desde Rust 1.74) define um padrão para todo o manifest que atributos inline ainda podem substituir localmente.
Essa camada é o modelo mental real a ser mantido: o nível de lint não é uma única configuração global, ele é resolvido por lint por escopo, o mais próximo vence, com uma exceção importante.
forbid é deliberadamente o nível mais forte porque, ao contrário de deny, ele não pode ser rebaixado para allow por nenhum escopo aninhado, que é por isso que equipes o utilizam para coisas como unsafe_code quando querem uma garantia que sobreviva a refatorações futuras por outros contribuidores.
Uma armadilha de raciocínio comum é tratar os grupos de lint do clippy como um interruptor "tudo ou nada".
Habilitar clippy::pedantic de forma generalizada em uma base de código existente grande geralmente produz centenas de novos avisos de valor variadamente útil, porque "pedantic" abrange tudo, desde capturas genuinamente úteis até preferências de estilo altamente subjetivas.
O padrão mais sustentável, visível nas outras páginas desta seção, é habilitar um grupo amplamente e então allow lints específicos e barulhentos por nome, de modo que o manifest documente uma política explícita e revisável em vez de uma exceção opaca geral.
Considerações Avançadas e Aplicações
Em escala, as decisões interessantes são menos sobre quais lints individuais habilitar e mais sobre onde a política deve residir e quão rigorosa ela deve ser por padrão.
Um crate de biblioteca consumido por muitos usuários downstream geralmente deseja uma política de lint conservadora, warn em vez de deny, porque um lint deny que o autor da biblioteca considera aceitável pode quebrar um build downstream no momento em que a definição desse lint muda em uma atualização do compilador.
Uma aplicação ou binário de serviço, por outro lado, é geralmente o último consumidor de seu próprio código, então equipes frequentemente deny lints como clippy::unwrap_used fora dos módulos de teste para impor uma disciplina de "trate seus erros" que uma biblioteca não pode impor com segurança aos seus chamadores.
Formatação e linting também se cruzam com o custo de CI e a experiência do desenvolvedor de maneiras que importam operacionalmente.
cargo fmt --check é barato e determinístico, então ele tipicamente controla o primeiro passo de CI; cargo clippy é mais caro porque ele reexecuta a inferência de tipos, então pipelines comumente cacheiam o diretório target/ e rodam clippy após a formatação já ter passado, evitando computação desperdiçada em código que será rejeitado por razões de estilo de qualquer maneira.
A integração com o editor via rust-analyzer fecha o ciclo mais cedo: rodar clippy como a fonte de diagnóstico ao salvar (em vez de um simples cargo check) expõe os mesmos lints que um gate de CI aplicaria, antes mesmo de um commit acontecer, o que encolhe o loop de feedback de minutos para segundos.
O sistema de lint também evoluiu em direção à centralização.
Projetos Rust antigos espalhavam #![allow(...)] e #![deny(...)] pelas raízes dos crates e flags de invocação de CI (-D warnings), o que tornava a política difícil de auditar porque ela vivia em vários lugares desconectados ao mesmo tempo.
A tabela de manifest [lints] moveu essa política para Cargo.toml, onde ela é versionada com o código, visível em um só lugar e (em um workspace Cargo) compartilhável entre crates membros, que é a direção para a qual o ecossistema tem se consolidado.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
Atributos inline #[allow]/#[deny] | Preciso, escopo para um item, auto-documentado com um comentário | Fácil de espalhar e perder de vista em uma base de código grande | Exceções únicas e bem justificadas |
Tabela [lints] do Cargo.toml | Centralizado, versionado, compartilhável em workspace | Granularidade mais grosseira do que uma única linha | Política para todo o crate ou workspace |
Flags apenas de CI (-D warnings, -W clippy::pedantic) | Nenhuma alteração de manifest necessária, fácil de experimentar | Invisível para cargo build local, fácil para execuções locais e de CI divergirem | Aumento temporário ou exploração |
Concepções Equivocadas Comuns
- "clippy substitui os próprios avisos do rustc" - clippy é aditivo; os lints próprios do
rustc(variáveis não usadas, código morto e similares) ainda rodam e ainda importam, clippy apenas adiciona um segundo conjunto, maior, por cima. - "rustfmt é configurável o suficiente para corresponder a qualquer guia de estilo de equipe" - as opções do
rustfmtsão intencionalmente estreitas porque o objetivo é um padrão compartilhado, não um formatador totalmente personalizável como algumas outras linguagens fornecem. - "Um nível de lint mais alto sempre significa código melhor" - políticas
deny-heavy em uma biblioteca podem quebrar builds downstream quando as regras de um lint mudam em atualizações do compilador, então a rigidez precisa ser combinada com quem consome o crate. - "
#[allow(...)]é uma rara saída de emergência" - em bases de código reais é comum e frequentemente correto, particularmente para limites de FFI ou código gerado, desde que cada um seja escopado de forma estreita e idealmente comentado com uma razão. - "Formatação e linting pegam bugs" - eles pegam desvios de estilo e uma classe de padrões suspeitos, mas compiláveis; nenhum substitui testes, e uma função perfeitamente formatada e limpa de lints ainda pode estar logicamente errada.
FAQs
Qual é a diferença real entre rustfmt e clippy?
rustfmtreescreve espaços em branco, quebras de linha e ordem de imports; ele nunca muda o comportamento do programa.clippyanalisa o programa com tipos verificados em busca de bugs prováveis, padrões não idiomáticos e problemas de performance; ele pode sugerir mudanças de código que preservam o comportamento, mas nunca as aplica a menos que você o execute com--fix.
Por que o Rust agrupa essas ferramentas em vez de deixá-las para a comunidade?
Agrupar remove um problema de coordenação: todo projeto Rust pode assumir que cargo fmt e cargo clippy existem e se comportam de forma idêntica, em vez de cada projeto ou empresa padronizar em um formatador ou linter de terceiros diferente.
Como o clippy realmente encontra problemas que o rustc perde?
Clippy reutiliza o parser e o type checker do rustc internamente, e então executa seus próprios passes de análise adicionais sobre esse programa já tipado, verificando padrões que o próprio conjunto de lints do rustc nunca foi projetado para sinalizar, como cadeias de iteradores ineficientes ou clones redundantes.
Como a severidade de um lint é realmente decidida em tempo de build?
Rust a resolve por escopo: a configuração mais próxima aplicável vence, verificando atributos inline no nível do item primeiro, depois atributos #![...] de módulo ou crate, depois a tabela [lints] do Cargo.toml como padrão, com forbid como um caso especial que não pode ser substituído por nada mais local.
Quando uma equipe NÃO deve ativar clippy::pedantic em todos os lugares?
Em uma base de código grande e estabelecida, habilitar pedantic de forma generalizada geralmente produz uma enxurrada de avisos de baixo valor que afogam os genuinamente úteis; ele se encaixa melhor em novos crates ou como um esforço deliberado e incremental de limpeza.
É alguma vez correto apenas permitir um lint em vez de corrigir o código?
Sim, particularmente em limites de FFI, em código gerado, ou quando a sugestão de um lint não se aplica a um protocolo específico ou restrição de hardware, desde que o allow seja escopado o mais estreitamente possível e idealmente carregue um comentário explicando o porquê.
Uma política de lint mais rigorosa não é sempre mais segura?
Não para crates de biblioteca: um lint deny que posteriormente muda de definição em uma atualização do compilador pode transformar uma dependência anteriormente aceitável em um build quebrado para todos os consumidores downstream, então bibliotecas geralmente tendem a warn e deixar as aplicações decidirem sua própria rigidez.
Por que colocar a política de lint em Cargo.toml em vez de um script de CI?
Uma tabela [lints] no manifest viaja com o código-fonte, aplica-se de forma idêntica se você rodar cargo build localmente ou em CI, e é visível para qualquer pessoa lendo o crate, enquanto uma flag -D warnings apenas de CI é invisível até que alguém abra a configuração do pipeline.
rustfmt ou clippy alguma vez mudam o que meu programa faz?
rustfmt nunca muda; ele apenas toca em espaços em branco e ordenação.
O próprio clippy não muda, a menos que você opte explicitamente por cargo clippy --fix, que aplica suas reescritas sugeridas e deve sempre ser revisado como um diff antes de commitar.
O que `forbid` faz que `deny` não faz?
Ambos falham o build, mas um lint de nível deny ainda pode ser localmente rebaixado com #[allow(...)] em um escopo aninhado, enquanto forbid não pode ser substituído em nenhum lugar downstream nessa unidade de compilação, que é por isso que ele é usado para garantias como "nenhum código unsafe" que devem sobreviver a edições futuras por outros contribuidores.
Por que clippy leva notavelmente mais tempo do que um build normal?
Ele executa seus passes de análise adicionais em cima da inferência de tipos completa em vez das verificações mais leves que cargo check realiza, então ele faz trabalho extra real; cachear o diretório target/ em CI e executá-lo após um rápido passo cargo fmt --check mantém o pipeline geral eficiente.
Essa ferramenta pega bugs de lógica ou problemas de segurança?
Apenas incidentalmente: alguns lints correctness e suspicious do clippy pegam bugs reais, mas as ferramentas são fundamentalmente verificadores de estilo e idiomáticos, não um substituto para testes, revisão de código ou auditoria de segurança dedicada.
Relacionados
- rustfmt - a camada de formatação que esta página introduz conceitualmente.
- Clippy - a camada de análise orientada a lint, com comandos concretos e categorias de lint.
- Níveis e Atributos de Lint - uma visão mais profunda e prática de allow/warn/deny/forbid e da tabela
[lints]. - Linting em CI - como essas ferramentas são aplicadas como um portão de build.
- Melhores Práticas de Linting e Formatação - recomendações de política de nível de equipe que se baseiam nesses conceitos.
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), com a tabela de manifest
[lints](estável desde Rust 1.74) eclippy/rustfmtcomo fornecidos com essa toolchain.