O Clippy é o linter oficial do Rust, oferecendo mais de 700 regras (lints) que ajudam a escrever código mais idiomático, correto e performático. Ele vai além do que o compilador verifica, detectando anti-patterns, código desnecessário, possíveis bugs e oportunidades de otimização.
O nome é uma referência ao assistente do Microsoft Office dos anos 90 — mas diferente daquele, as sugestões do Clippy são genuinamente úteis. Ele é mantido pela equipe oficial do Rust e faz parte da distribuição padrão, sendo uma ferramenta indispensável para qualquer projeto Rust sério.
Neste guia, vamos explorar como usar o Clippy efetivamente, configurar suas regras, integrá-lo ao CI/CD e entender os 20 lints mais importantes que todo desenvolvedor Rust deve conhecer.
Instalação
O Clippy geralmente já vem instalado com o Rust. Se não estiver, instale via rustup:
# Instalar Clippy (normalmente já incluso)
rustup component add clippy
# Verificar instalação
cargo clippy --version
# clippy 0.1.77 (aedd173a2 2024-03-17)
# Atualizar (atualiza junto com o Rust)
rustup update stable
Uso Básico
Executando o Clippy
# Executar Clippy no projeto
cargo clippy
# Verificar todos os targets (testes, benchmarks, exemplos)
cargo clippy --all-targets
# Verificar com todas as features ativadas
cargo clippy --all-features
# Combinação mais completa
cargo clippy --workspace --all-targets --all-features
# Tratar warnings como erros (ideal para CI)
cargo clippy -- -D warnings
# Aplicar correções automaticamente
cargo clippy --fix
# Aplicar correções permitindo mudanças não-staged
cargo clippy --fix --allow-dirty --allow-staged
Entendendo a Saída
warning: redundant clone
--> src/main.rs:10:25
|
10 | let nome = texto.clone();
| ^^^^^^^^
|
= note: `#[warn(clippy::redundant_clone)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
help: remove this
|
10 - let nome = texto.clone();
10 + let nome = texto;
|
Cada aviso mostra:
- Categoria e nome do lint (
clippy::redundant_clone) - Localização no código (arquivo, linha, coluna)
- Explicação do problema
- Link para documentação detalhada
- Sugestão de correção quando possível
Controlando Lints no Código
// Permitir um lint específico em um item
#[allow(clippy::too_many_arguments)]
fn funcao_complexa(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32) {
// ...
}
// Permitir um lint para todo o módulo
#![allow(clippy::module_name_repetitions)]
// Negar (tornar erro) um lint específico
#[deny(clippy::unwrap_used)]
fn funcao_critica() {
// unwrap() aqui causará erro de compilação
}
// Avisar sobre um lint que normalmente está desativado
#[warn(clippy::nursery)]
fn funcao_experimental() {
// ...
}
// Permitir com justificativa (boa prática)
#[allow(clippy::cast_possible_truncation)]
// Sabemos que o valor sempre cabe em u32 porque vem de um campo de 16 bits
fn converter_valor(v: u64) -> u32 {
v as u32
}
// Suprimir inline
fn exemplo() {
#[allow(clippy::needless_collect)]
let items: Vec<_> = (0..10).collect();
// ...
}
Recursos Avançados
Categorias de Lints
O Clippy organiza seus lints em categorias:
# Ativar/desativar categorias inteiras
cargo clippy -- -W clippy::pedantic # Ativar pedantic como warnings
cargo clippy -- -D clippy::correctness # Negar (erro) lints de correção
cargo clippy -- -A clippy::style # Permitir lints de estilo
| Categoria | Descrição | Padrão | Exemplos |
|---|---|---|---|
clippy::correctness | Código provavelmente incorreto | Deny | approx_constant, invalid_regex |
clippy::suspicious | Código suspeito que pode ser bug | Warn | suspicious_map, suspicious_else_formatting |
clippy::style | Código não idiomático | Warn | needless_return, redundant_closure |
clippy::complexity | Código desnecessariamente complexo | Warn | needless_bool, useless_format |
clippy::perf | Código com problemas de performance | Warn | inefficient_to_string, manual_memcpy |
clippy::pedantic | Lints mais rigorosos | Allow | must_use_candidate, doc_markdown |
clippy::nursery | Lints experimentais | Allow | missing_const_for_fn, use_self |
clippy::restriction | Lints opinionados (usar seletivamente) | Allow | unwrap_used, expect_used |
clippy::cargo | Problemas no Cargo.toml | Allow | multiple_crate_versions, wildcard_dependencies |
Configurando com clippy.toml
Crie um arquivo clippy.toml ou .clippy.toml na raiz do projeto:
# clippy.toml
# Limiar para funções cognitivamente complexas
cognitive-complexity-threshold = 30
# Número máximo de argumentos em uma função
too-many-arguments-threshold = 8
# Número máximo de linhas em uma função
too-many-lines-threshold = 150
# Limiar para tipos grandes em enum variants
enum-variant-size-threshold = 200
# Tipos que devem ser considerados triviais para copy
trivial-copy-size-limit = 32
# Número máximo de variantes de enum
enum-variant-name-threshold = 3
# Prefixo de módulo permitido
allowed-prefixes = ["to", "from", "into", "as", "try"]
# Tipos que devem ser considerados "large" para box
type-complexity-threshold = 250
# Palavras permitidas em doc comments
doc-valid-idents = [
"GitHub",
"GitLab",
"JavaScript",
"TypeScript",
"PostgreSQL",
"MongoDB",
"WebSocket",
"OAuth",
"GraphQL",
"DevOps",
"macOS",
"iOS",
]
# Macros que devem ser ignoradas pelo Clippy
disallowed-macros = [
{ path = "std::todo", reason = "Não deve estar em código de produção" },
{ path = "std::unimplemented", reason = "Use proper error handling" },
]
# Métodos que não devem ser usados
disallowed-methods = [
{ path = "std::env::var", reason = "Use config crate em vez de env vars diretas" },
]
# Tipos que não devem ser usados
disallowed-types = [
{ path = "std::collections::LinkedList", reason = "Use Vec ou VecDeque" },
]
# msrv - versão mínima do Rust suportada
msrv = "1.70.0"
# Limiar para literal numbers
literal-suffix-style = "separated"
Configuração no Cargo.toml
# Cargo.toml - seção de lints (Rust 1.74+)
[lints.clippy]
# Ativar categorias
pedantic = "warn"
nursery = "warn"
# Desativar lints específicos do pedantic
module_name_repetitions = "allow"
must_use_candidate = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
# Negar lints importantes
unwrap_used = "deny"
expect_used = "deny"
panic = "deny"
todo = "deny"
# Lints de performance como erro
inefficient_to_string = "deny"
large_enum_variant = "warn"
[lints.rust]
unsafe_code = "deny"
missing_docs = "warn"
Integração com CI/CD
# .github/workflows/clippy.yml
name: Clippy
on: [push, pull_request]
jobs:
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
# Clippy com todas as verificações
- name: Executar Clippy
run: |
cargo clippy --workspace --all-targets --all-features -- -D warnings
# Clippy como SARIF para GitHub Code Scanning
clippy-sarif:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo install clippy-sarif sarif-fmt
- run: |
cargo clippy --all-features --message-format=json |
clippy-sarif | tee results.sarif | sarif-fmt
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
Pre-commit Hook
#!/bin/bash
# .git/hooks/pre-commit
echo "Executando Clippy..."
cargo clippy --all-targets --all-features -- -D warnings
if [ $? -ne 0 ]; then
echo "Clippy encontrou problemas. Corrija antes de commitar."
exit 1
fi
Com a ferramenta pre-commit:
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: clippy
name: clippy
entry: cargo clippy --all-targets --all-features -- -D warnings
language: system
types: [rust]
pass_filenames: false
Boas Práticas
1. Ative Pedantic para Bibliotecas Públicas
// src/lib.rs
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::must_use_candidate)]
2. Use Restriction Seletivamente
// Para código crítico, ative lints de restriction específicos
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]
#![deny(clippy::todo)]
#![deny(clippy::unimplemented)]
#![warn(clippy::print_stdout)] // Proibir println! em bibliotecas
#![warn(clippy::print_stderr)]
3. Documente Suppressões
// Sempre explique por que está suprimindo um lint
#[allow(clippy::cast_possible_truncation)]
// SAFETY: max_connections é limitado a 1024 pela validação de config,
// portanto sempre cabe em u16
fn max_conn_as_u16(config: &Config) -> u16 {
config.max_connections as u16
}
4. Execute Clippy no CI com -D warnings
# Sempre trate warnings como erros no CI
cargo clippy --workspace --all-targets --all-features -- -D warnings
5. Atualize Regularmente
Novas versões do Clippy adicionam lints úteis. Mantenha atualizado:
rustup update stable
Exemplos Práticos
Top 20 Lints Mais Úteis do Clippy
1. needless_return - Retorno desnecessário
// Antes (Clippy avisa)
fn soma(a: i32, b: i32) -> i32 {
return a + b; // return explícito desnecessário
}
// Depois (idiomático)
fn soma(a: i32, b: i32) -> i32 {
a + b // última expressão é o retorno
}
2. redundant_clone - Clone desnecessário
// Antes
fn processar(texto: String) -> String {
let resultado = texto.clone(); // clone desnecessário, texto é owned
resultado
}
// Depois
fn processar(texto: String) -> String {
texto // já é owned, não precisa clonar
}
3. needless_collect - Coleta desnecessária
// Antes
fn soma_pares(numeros: &[i32]) -> i32 {
let pares: Vec<i32> = numeros.iter()
.filter(|n| *n % 2 == 0)
.copied()
.collect(); // coleta desnecessária
pares.iter().sum()
}
// Depois
fn soma_pares(numeros: &[i32]) -> i32 {
numeros.iter()
.filter(|n| *n % 2 == 0)
.sum()
}
4. manual_map - Map manual
// Antes
fn duplicar_opcional(valor: Option<i32>) -> Option<i32> {
match valor {
Some(v) => Some(v * 2),
None => None,
}
}
// Depois
fn duplicar_opcional(valor: Option<i32>) -> Option<i32> {
valor.map(|v| v * 2)
}
5. unwrap_used - Uso de unwrap
// Antes (pode causar panic)
fn ler_config(caminho: &str) -> String {
std::fs::read_to_string(caminho).unwrap()
}
// Depois (tratamento adequado)
fn ler_config(caminho: &str) -> Result<String, std::io::Error> {
std::fs::read_to_string(caminho)
}
// Ou com contexto
fn ler_config_ctx(caminho: &str) -> anyhow::Result<String> {
std::fs::read_to_string(caminho)
.map_err(|e| anyhow::anyhow!("Falha ao ler config '{}': {}", caminho, e))
}
6. inefficient_to_string - ToString ineficiente
// Antes
let texto = &format!("{}", numero); // ineficiente
// Depois
let texto = &numero.to_string(); // mais direto
7. needless_bool - Booleano desnecessário
// Antes
fn eh_positivo(n: i32) -> bool {
if n > 0 {
true
} else {
false
}
}
// Depois
fn eh_positivo(n: i32) -> bool {
n > 0
}
8. useless_format - Format desnecessário
// Antes
let nome = format!("Maria"); // format! sem argumentos
// Depois
let nome = "Maria".to_string(); // ou String::from("Maria")
9. iter_next_slice - Acesso via iterador em slices
// Antes
let primeiro = vec.iter().next();
// Depois
let primeiro = vec.first();
10. manual_flatten - Flatten manual
// Antes
for item in lista {
if let Some(valor) = item {
processar(valor);
}
}
// Depois
for valor in lista.into_iter().flatten() {
processar(valor);
}
11. single_match - Match com um único braço
// Antes
match resultado {
Ok(valor) => processar(valor),
_ => {},
}
// Depois
if let Ok(valor) = resultado {
processar(valor);
}
12. map_flatten - Map seguido de flatten
// Antes
let resultado: Vec<i32> = listas.iter()
.map(|l| l.iter().copied())
.flatten()
.collect();
// Depois
let resultado: Vec<i32> = listas.iter()
.flat_map(|l| l.iter().copied())
.collect();
13. large_enum_variant - Variante grande de enum
// Antes (variantes com tamanhos muito diferentes)
enum Mensagem {
Texto(String), // ~24 bytes
Dados([u8; 4096]), // 4096 bytes!
}
// Depois (use Box para variantes grandes)
enum Mensagem {
Texto(String),
Dados(Box<[u8; 4096]>), // ~8 bytes (ponteiro)
}
14. clone_on_copy - Clone em tipo Copy
// Antes
let x: i32 = 42;
let y = x.clone(); // i32 implementa Copy, clone é desnecessário
// Depois
let x: i32 = 42;
let y = x; // cópia implícita
15. redundant_closure - Closure redundante
// Antes
let numeros: Vec<String> = (1..10)
.map(|n| n.to_string()) // closure desnecessária
.collect();
// Depois
let numeros: Vec<String> = (1..10)
.map(i32::to_string) // referência direta ao método
.collect();
16. match_wildcard_for_single_variants - Wildcard para variante única
enum Cor { Vermelho, Azul, Verde }
// Antes
match cor {
Cor::Vermelho => println!("vermelho"),
_ => println!("outro"), // esconde que são Azul e Verde
}
// Depois
match cor {
Cor::Vermelho => println!("vermelho"),
Cor::Azul | Cor::Verde => println!("outro"), // explícito
}
17. string_add - Concatenação ineficiente
// Antes
let saudacao = "Olá, ".to_string() + &nome + "!";
// Depois
let saudacao = format!("Olá, {nome}!");
18. implicit_clone - Clone implícito
// Antes
let copia = texto.to_owned(); // em &String, equivale a clone
let copia = texto.to_string(); // em &String, equivale a clone
// Depois (mais claro)
let copia = texto.clone();
19. or_fun_call - Chamada de função em or()
// Antes (aloca String mesmo quando não necessário)
fn obter_nome(nome: Option<String>) -> String {
nome.unwrap_or("padrão".to_string())
}
// Depois (lazy - só aloca se necessário)
fn obter_nome(nome: Option<String>) -> String {
nome.unwrap_or_else(|| "padrão".to_string())
}
20. approx_constant - Constante aproximada
// Antes
let pi = 3.14159265;
// Depois
let pi = std::f64::consts::PI;
Exemplo Completo: Projeto com Clippy Configurado
// src/lib.rs
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::must_use_candidate)]
use std::collections::HashMap;
/// Resultado de uma análise de texto
#[derive(Debug, Clone)]
pub struct AnaliseTexto {
/// Contagem de cada palavra
pub contagem_palavras: HashMap<String, usize>,
/// Número total de palavras
pub total_palavras: usize,
/// Número de palavras únicas
pub palavras_unicas: usize,
/// Palavra mais frequente
pub mais_frequente: Option<(String, usize)>,
}
/// Analisa um texto e retorna estatísticas
///
/// # Errors
///
/// Retorna erro se o texto estiver vazio
pub fn analisar_texto(texto: &str) -> Result<AnaliseTexto, &'static str> {
if texto.trim().is_empty() {
return Err("Texto não pode ser vazio");
}
let contagem_palavras: HashMap<String, usize> = texto
.split_whitespace()
.map(|p| p.to_lowercase())
.map(|p| p.trim_matches(|c: char| !c.is_alphanumeric()).to_string())
.filter(|p| !p.is_empty())
.fold(HashMap::new(), |mut acc, palavra| {
*acc.entry(palavra).or_insert(0) += 1;
acc
});
let total_palavras = contagem_palavras.values().sum();
let palavras_unicas = contagem_palavras.len();
let mais_frequente = contagem_palavras
.iter()
.max_by_key(|(_, &count)| count)
.map(|(palavra, &count)| (palavra.clone(), count));
Ok(AnaliseTexto {
contagem_palavras,
total_palavras,
palavras_unicas,
mais_frequente,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn teste_analise_basica() {
let resultado = analisar_texto("olá mundo olá rust").expect("deveria analisar");
assert_eq!(resultado.total_palavras, 4);
assert_eq!(resultado.palavras_unicas, 3);
}
#[test]
fn teste_texto_vazio() {
let resultado = analisar_texto("");
assert!(resultado.is_err());
}
#[test]
fn teste_palavra_mais_frequente() {
let resultado = analisar_texto("rust rust rust python java").expect("deveria analisar");
let (palavra, contagem) = resultado.mais_frequente.expect("deveria ter mais frequente");
assert_eq!(palavra, "rust");
assert_eq!(contagem, 3);
}
}
Comparação com Alternativas
| Ferramenta | Linguagem | Lints | Auto-fix | Configurável | Integração CI |
|---|---|---|---|---|---|
| Clippy | Rust | 700+ | Sim | Sim | Excelente |
| ESLint | JavaScript | 300+ | Sim | Muito | Excelente |
| Pylint | Python | 400+ | Parcial | Sim | Boa |
| RuboCop | Ruby | 400+ | Sim | Sim | Boa |
| golangci-lint | Go | 100+ | Parcial | Sim | Excelente |
| ktlint | Kotlin | 100+ | Sim | Sim | Boa |
O Clippy se destaca por:
- Integração nativa com o compilador (acesso a tipos e lifetime info)
- Zero falsos positivos nos lints de
correctness - Sugestões de correção precisas e aplicáveis automaticamente
- Categorização clara que facilita adoção gradual
- Manutenção oficial pela equipe Rust
Conclusão
O Clippy é uma ferramenta indispensável para escrever código Rust de alta qualidade. Com mais de 700 lints organizados em categorias claras, ele ajuda desde iniciantes aprendendo padrões idiomáticos até especialistas otimizando código de produção.
Pontos-chave para lembrar:
- Execute sempre com
--all-targets --all-featurespara cobertura completa - Use
-D warningsno CI para evitar regressões - Ative
pedanticpara bibliotecas públicas - Configure
clippy.tomlpara personalizar thresholds - Documente suppressões com comentários explicativos
- Use
--fixpara aplicar correções automaticamente
Para explorar todos os lints disponíveis, visite o Clippy Lint Index.
No próximo passo, aprenda sobre o Rustfmt para manter seu código formatado de forma consistente.