Clippy: O Linter Oficial do Rust

Guia completo do Clippy, o linter oficial do Rust. Aprenda sobre categorias de lints, configuração, integração com CI/CD e os 20 lints mais úteis com exemplos práticos.

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:

  1. Categoria e nome do lint (clippy::redundant_clone)
  2. Localização no código (arquivo, linha, coluna)
  3. Explicação do problema
  4. Link para documentação detalhada
  5. 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
CategoriaDescriçãoPadrãoExemplos
clippy::correctnessCódigo provavelmente incorretoDenyapprox_constant, invalid_regex
clippy::suspiciousCódigo suspeito que pode ser bugWarnsuspicious_map, suspicious_else_formatting
clippy::styleCódigo não idiomáticoWarnneedless_return, redundant_closure
clippy::complexityCódigo desnecessariamente complexoWarnneedless_bool, useless_format
clippy::perfCódigo com problemas de performanceWarninefficient_to_string, manual_memcpy
clippy::pedanticLints mais rigorososAllowmust_use_candidate, doc_markdown
clippy::nurseryLints experimentaisAllowmissing_const_for_fn, use_self
clippy::restrictionLints opinionados (usar seletivamente)Allowunwrap_used, expect_used
clippy::cargoProblemas no Cargo.tomlAllowmultiple_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

FerramentaLinguagemLintsAuto-fixConfigurávelIntegração CI
ClippyRust700+SimSimExcelente
ESLintJavaScript300+SimMuitoExcelente
PylintPython400+ParcialSimBoa
RuboCopRuby400+SimSimBoa
golangci-lintGo100+ParcialSimExcelente
ktlintKotlin100+SimSimBoa

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-features para cobertura completa
  • Use -D warnings no CI para evitar regressões
  • Ative pedantic para bibliotecas públicas
  • Configure clippy.toml para personalizar thresholds
  • Documente suppressões com comentários explicativos
  • Use --fix para 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.