Bem-vindo à aula sobre Clippy e rustfmt, duas ferramentas indispensáveis no ecossistema Rust. Elas ajudam a manter seu código limpo, consistente e livre de erros comuns. Enquanto o rustfmt cuida da formatação (espaços, quebras de linha, indentação), o Clippy analisa seu código em busca de padrões problemáticos, oferecendo sugestões de melhoria. Nesta aula, você aprenderá a configurá-los, usá-los no dia a dia e integrá-los ao seu fluxo de trabalho, incluindo pipelines de CI.

Essas ferramentas são parte do toolchain padrão do Rust e podem ser instaladas com o rustup. Elas são amplamente adotadas pela comunidade e frequentemente usadas em projetos open source, então dominá-las é essencial para colaborar em projetos reais.

Lints úteis

Clippy é o linter oficial do Rust. Ele contém uma coleção de lints que vão além do compilador, detectando estilos de código que podem ser simplificados, propensos a erros ou menos eficientes. Você pode executá-lo com cargo clippy, e ele analisará todos os alvos do seu projeto.

Alguns lints são extremamente úteis no dia a dia. Por exemplo, clippy::single_match sugere usar if let em vez de match quando há apenas um braço relevante. clippy::needless_return aponta quando o return é desnecessário, e clippy::collapsible_if sugere combinar ifs aninhados. Vamos ver um exemplo:

fn main() {
    let x = 5;
    match x {
        0 => println!("zero"),
        _ => println!("não-zero"),
    }
}

Clippy sugerirá usar if let para o caso 0, pois o outro braço é capturado por _. Isso torna o código mais conciso e legível.

Outro lint útil é clippy::map_clone, que sugere usar cloned() em iteradores quando você está mapeando apenas para clonar. Além disso, clippy::needless_range_loop recomenda usar iteradores em vez de índices quando possível. Esses lints ajudam a escrever código mais idiomático e eficiente.

Você pode controlar a severidade dos lints no lib.rs ou main.rs com atributos, ou no arquivo Cargo.toml usando a seção [lints.clippy]. Por exemplo, para permitir um lint específico em todo o crate, você pode usar:

#![allow(clippy::needless_return)]

Ou, para negar (tornar erro) um lint, use #![deny(clippy::unwrap_used)], o que é útil em projetos críticos.

Formatação automática

O rustfmt é o formatador oficial do Rust. Ele garante que todo o código siga um estilo consistente, eliminando discussões sobre formatação em code reviews. Para formatar um projeto, basta executar cargo fmt, que formata todos os arquivos .rs no diretório atual e subdiretórios.

O estilo padrão do rustfmt é baseado nas diretrizes oficiais da comunidade. Por exemplo, ele usa 4 espaços para indentação, chaves na mesma linha para blocos, e espaços ao redor de operadores. Aqui está um exemplo de código não formatado:

fn add(a:i32,b:i32)->i32{a+b}

Após rodar cargo fmt, o código se torna:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

Isso torna a leitura muito mais fácil. O rustfmt também pode ser usado como uma biblioteca, mas a forma mais comum é via CLI. Ele pode ser configurado através de um arquivo rustfmt.toml ou .rustfmt.toml no diretório raiz do projeto.

Algumas opções de configuração incluem max_width (largura máxima da linha, padrão 100), tab_spaces (número de espaços por tab), e use_field_init_shorthand (se deve usar a sintaxe abreviada de inicialização de campos). É importante notar que o rustfmt não faz alterações que mudem o comportamento do código, apenas a formatação.

Integração CI

Integrar Clippy e rustfmt em seu pipeline de Integração Contínua (CI) é uma prática recomendada. Isso garante que todo o código que entra no repositório esteja formatado e livre de lints problemáticos. Você pode adicionar etapas no seu CI (como GitHub Actions, GitLab CI, etc.) para executar cargo fmt --check e cargo clippy -- -D warnings.

O comando cargo fmt --check verifica se o código está formatado sem modificar nada, retornando um erro se houver diferenças. Já cargo clippy -- -D warnings trata todos os avisos do Clippy como erros, fazendo o build falhar se houver qualquer lint.

Um exemplo de workflow no GitHub Actions seria:

name: CI

on: [push, pull_request]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy
      - run: cargo fmt --check
      - run: cargo clippy -- -D warnings

Essa configuração garante que qualquer PR que não esteja formatado ou contenha lints falhe no CI, mantendo a base de código limpa.

Além disso, você pode configurar o Clippy para negar certos lints no CI, usando a seção [lints] no Cargo.toml. Isso centraliza as políticas de lint do projeto.

Configuração

Tanto o Clippy quanto o rustfmt podem ser configurados de forma granular. Para o rustfmt, você pode criar um arquivo rustfmt.toml na raiz do projeto. Por exemplo, para definir uma largura máxima de 80 caracteres e usar 2 espaços de indentação:

max_width = 80
tab_spaces = 2

Já o Clippy é configurado principalmente através de atributos no código ou na seção [lints.clippy] do Cargo.toml. Por exemplo, para permitir o lint clippy::too_many_arguments em todo o crate:

[lints.clippy]
too_many_arguments = "allow"

Você também pode configurar níveis de severidade: allow, warn, deny e forbid. É comum usar deny para lints que você considera críticos.

Além disso, você pode desabilitar completamente o Clippy ou o rustfmt em um trecho de código usando #[allow(clippy::some_lint)] ou #[rustfmt::skip] para pular a formatação de um item específico. Isso é útil em casos excepcionais, como código gerado por macros.

Uma boa prática é configurar seu editor para rodar o rustfmt automaticamente ao salvar e o Clippy em segundo plano. Isso evita acumular problemas e facilita a adesão aos padrões do projeto.

Boas práticas e observações finais

Essas ferramentas são projetadas para serem usadas em conjunto. Enquanto o rustfmt cuida da forma, o Clippy cuida do conteúdo. Aqui estão algumas boas práticas:

  • Execute cargo fmt e cargo clippy regularmente, de preferência antes de cada commit.
  • Use cargo fmt --check no CI para garantir que o código está formatado.
  • Trate os avisos do Clippy como erros no CI usando -D warnings.
  • Configure o Clippy com lints adicionais, como clippy::pedantic e clippy::nursery, para análises mais rigorosas.
  • Documente as configurações do projeto em um arquivo rustfmt.toml e na seção [lints] do Cargo.toml para que todos os contribuidores sigam o mesmo estilo.

Lembre-se de que essas ferramentas são opiniáticas, mas a consistência que elas trazem é valiosa para projetos de qualquer tamanho.

Referências

Exercícios

  1. Exercício 1: Crie um projeto Rust simples com um arquivo main.rs contendo uma função que recebe dois números e retorna o maior. Escreva o código de forma não formatada (ex.: sem espaços, chaves na mesma linha) e depois execute cargo fmt. Mostre o antes e o depois.

✓ Resposta:

Exemplo de código não formatado:

fn max(a:i32,b:i32)->i32{if a>b{a}else{b}}

Após cargo fmt:

fn max(a: i32, b: i32) -> i32 {
    if a > b { a } else { b }
}
  • Exercício 2: Escreva um trecho de código que o Clippy sinalize com o lint clippy::single_match. Execute cargo clippy e mostre a sugestão. Corrija o código conforme a sugestão.
  • ✓ Resposta:

    Código problemático:

    fn main() {
        let x = 10;
        match x {
            0 => println!("zero"),
            _ => println!("outro"),
        }
    }

    Clippy sugere usar if let:

    fn main() {
        let x = 10;
        if let 0 = x {
            println!("zero");
        } else {
            println!("outro");
        }
    }
  • Exercício 3: Configure um arquivo rustfmt.toml para que o rustfmt use largura máxima de 80 caracteres e indentação de 2 espaços. Crie um exemplo de código que seja reformatado de acordo com essa configuração.
  • ✓ Resposta:

    Arquivo rustfmt.toml:

    max_width = 80
    tab_spaces = 2
    

    Exemplo de código antes:

    fn main() { let x = 1; let y = 2; println!("{}", x + y); }

    Depois:

    fn main() {
      let x = 1;
      let y = 2;
      println!("{}", x + y);
    }
  • Exercício 4: Explique como você integraria o Clippy e o rustfmt em um pipeline de CI usando GitHub Actions. Escreva um exemplo de workflow YAML que execute cargo fmt --check e cargo clippy -- -D warnings.
  • ✓ Resposta:

    Exemplo de workflow:

    name: CI
    
    on: [push, pull_request]
    
    jobs:
      check:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: dtolnay/rust-toolchain@stable
            with:
              components: rustfmt, clippy
          - run: cargo fmt --check
          - run: cargo clippy -- -D warnings
    
  • Exercício 5: Crie um trecho de código que contenha um lint da categoria clippy::pedantic (por exemplo, clippy::cast_possible_truncation) e explique como você poderia configurá-lo para ser permitido ou negado no projeto.
  • ✓ Resposta:

    Código com possível truncamento:

    fn main() {
        let x: i64 = 1000;
        let y = x as i32;
        println!("{}", y);
    }

    Para permitir esse lint no projeto, adicione no Cargo.toml:

    [lints.clippy]
    cast_possible_truncation = "allow"
    

    Ou negar com "deny".