Clippy Rust: cargo clippy, Lints e CI

Aprenda a usar cargo clippy em Rust: instale o linter, corrija warnings, configure lints e rode no CI. Veja comandos e exemplos práticos em português no guia.

Clippy é o linter do Rust: execute cargo clippy na pasta do Cargo.toml para encontrar possíveis bugs, padrões não idiomáticos e oportunidades de melhoria. Ele complementa o compilador com centenas de regras (lints). Para começar, instale o componente com rustup component add clippy; para incluir testes e exemplos na análise, use cargo clippy --all-targets.

Cargo clippy: qual comando usar?

Sua tarefaComandoAtenção
Analisar o projeto atualcargo clippyExecute na pasta do Cargo.toml
Incluir testes, exemplos e benchmarkscargo clippy --all-targetsNão executa os testes; apenas analisa o código
Analisar todos os membros do workspacecargo clippy --workspace --all-targetsUsa as features padrão
Fazer o CI falhar em warningscargo clippy --workspace --all-targets -- -D warnings-- separa argumentos do Cargo e do compilador
Analisar sem features padrãocargo clippy --no-default-featuresÚtil para bibliotecas com configurações mínimas
Escolher uma featurecargo clippy --features nome-da-featureUse o nome definido no seu Cargo.toml
Aplicar sugestões automáticascargo clippy --fixComece com o Git limpo e revise o diff

Clippy não substitui os testes nem o rustfmt. Use cargo test para executar testes e cargo fmt --check para verificar a formatação. O guia de Rustfmt explica a diferença; o guia de Cargo ajuda com workspaces e features.

--all-features ativa todas as features de uma só vez, não testa cada combinação. Se o projeto tem features incompatíveis entre si, rode Clippy separadamente para as configurações suportadas. Antes de usar --fix, faça um commit ou salve seu trabalho: a opção altera arquivos e não resolve todos os avisos. Depois, revise git diff e execute os testes.

Se aparecer “no such command: clippy”, rode rustup component add clippy para a toolchain em uso. Projetos com rust-toolchain.toml podem selecionar uma versão diferente da padrão; confira rustup show active-toolchain. Para comandos oficiais, consulte a documentação de uso do Clippy.

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
# A versão exibida acompanha a toolchain Rust instalada

# 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

# Todas as features juntas (somente se forem compatíveis entre si)
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 = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }

# 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"

Os grupos pedantic e nursery têm prioridade -1 para que as exceções individuais, cuja prioridade padrão é 0, prevaleçam. Sem isso, grupo e lint ficam na mesma prioridade; a ordem das linhas no TOML não define a precedência. clippy.toml configura parâmetros dos lints, enquanto [lints.clippy] define quais regras avisam, falham ou são permitidas. Consulte a configuração oficial do Clippy.

Integração com CI/CD

O exemplo abaixo usa --all-features: remova essa opção ou crie verificações separadas se houver features incompatíveis. Fixe a toolchain do projeto quando precisar evitar mudanças inesperadas de lints no CI.

# .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

fn duplicar(n: i32) -> i32 {
    n * 2
}

// Antes
let numeros: Vec<i32> = (1..10)
    .map(|n| duplicar(n))
    .collect();

// Depois: a função aceita o mesmo tipo entregue pelo iterador
let numeros: Vec<i32> = (1..10)
    .map(duplicar)
    .collect();

Nem toda closure pode ser substituída diretamente por um método: i32::to_string recebe &i32, mas um range de inteiros entrega valores. Confira a assinatura antes de aplicar a mudança.

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)
  • Lints de correctness voltados a problemas de correção; ainda assim, revise cada diagnóstico no contexto do projeto
  • 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:

  • Use --all-targets para analisar também testes e exemplos; adicione --all-features apenas quando as features forem compatíveis
  • 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.