O Rustfmt é o formatador oficial de código Rust, responsável por aplicar um estilo consistente a todo o código de forma automática. Ele elimina debates sobre estilo de código em equipes, garante que diffs em pull requests mostrem apenas mudanças reais de lógica, e permite que desenvolvedores foquem na resolução de problemas em vez de formatação.
Assim como o gofmt revolucionou a comunidade Go ao eliminar guerras de estilo, o Rustfmt faz o mesmo pelo ecossistema Rust. A grande maioria dos projetos Rust open-source utiliza o Rustfmt com as configurações padrão, criando uma experiência de leitura consistente em toda a comunidade.
Neste guia, vamos explorar como configurar o Rustfmt, personalizar suas opções, integrá-lo com editores e pipelines de CI/CD, e estabelecer boas práticas para seu projeto.
Instalação
O Rustfmt geralmente já vem instalado com o Rust. Se não estiver:
# Instalar Rustfmt via rustup
rustup component add rustfmt
# Verificar instalação
rustfmt --version
# rustfmt 1.7.0-stable (aedd173a2 2024-03-17)
# Também disponível via cargo
cargo fmt --version
Uso Básico
Formatando Código
# Formatar todo o projeto
cargo fmt
# Formatar todo o workspace
cargo fmt --all
# Verificar se o código está formatado (sem modificar)
cargo fmt -- --check
cargo fmt --all -- --check
# Formatar um arquivo específico
rustfmt src/main.rs
rustfmt src/lib.rs src/utils.rs
# Mostrar diferenças em vez de aplicar
cargo fmt -- --check --color always
# Formatar com output detalhado
cargo fmt -- --verbose
# Formatar e mostrar o que mudou
rustfmt --emit files src/main.rs
rustfmt --emit stdout src/main.rs # imprime para stdout
Exemplo de Formatação
Antes do Rustfmt:
use std::collections::HashMap;use std::io::{self,Read,Write};
fn main(){let mut mapa=HashMap::new();
mapa.insert("chave",42);mapa.insert("outra",100);
for(k,v) in &mapa{
println!("{}: {}",k,v);
}
if mapa.len()>1{println!("Múltiplas entradas");}else{println!("Uma entrada");}
let resultado=match mapa.get("chave"){Some(v)=>v*2,None=>0,};
println!("Resultado: {}",resultado);
}
Depois do Rustfmt:
use std::collections::HashMap;
use std::io::{self, Read, Write};
fn main() {
let mut mapa = HashMap::new();
mapa.insert("chave", 42);
mapa.insert("outra", 100);
for (k, v) in &mapa {
println!("{}: {}", k, v);
}
if mapa.len() > 1 {
println!("Múltiplas entradas");
} else {
println!("Uma entrada");
}
let resultado = match mapa.get("chave") {
Some(v) => v * 2,
None => 0,
};
println!("Resultado: {}", resultado);
}
Suprimindo Formatação
Às vezes, a formatação manual é mais legível:
// Pular um bloco de código
#[rustfmt::skip]
fn matriz_transformacao() -> [[f64; 4]; 4] {
[
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
}
// Pular formatação em um módulo inteiro
#[rustfmt::skip]
mod tabelas_lookup {
pub const CORES: &[&str] = &[
"vermelho", "azul", "verde",
"amarelo", "laranja", "roxo",
"branco", "preto", "cinza",
];
}
// Pular um único item
#[rustfmt::skip]
let flags = FLAG_A | FLAG_B
| FLAG_C | FLAG_D
| FLAG_E;
// Pular formatação de macro
#[rustfmt::skip]
macro_rules! minha_macro {
($($x:expr),*) => {
vec![$($x),*]
};
}
Recursos Avançados
Configuração com rustfmt.toml
Crie um arquivo rustfmt.toml ou .rustfmt.toml na raiz do projeto:
# rustfmt.toml - Configuração completa
# === Indentação e Largura ===
# Largura máxima da linha (padrão: 100)
max_width = 100
# Largura de indentação em espaços (padrão: 4)
tab_spaces = 4
# Usar hard tabs em vez de espaços (padrão: false)
hard_tabs = false
# === Imports ===
# Agrupamento de imports (padrão: Preserve)
# StdExternalCrate: separa std, external, crate
# One: agrupa tudo
# Preserve: mantém como está
group_imports = "StdExternalCrate"
# Mesclar imports do mesmo módulo (padrão: Preserve)
imports_granularity = "Crate"
# === Estilo de Código ===
# Edição do Rust (padrão: 2015)
edition = "2021"
# Posição da chave de abertura
# SameLineWhere: mesma linha, exceto com where
# AlwaysNextLine: sempre na próxima linha
brace_style = "SameLineWhere"
# Posição do else
# Same: else na mesma linha do }
# NextLine: else na próxima linha
control_brace_style = "AlwaysSameLine"
# === Comentários ===
# Normalizar comentários (padrão: false)
normalize_comments = false
# Normalizar doc comments (padrão: false)
normalize_doc_attributes = false
# Envolver comentários para caber na largura máxima (padrão: false)
wrap_comments = false
# === Funções e Chamadas ===
# Largura máxima para argumentos de função em uma linha
fn_args_layout = "Tall"
# Chamadas de função em uma única linha se couberem
fn_single_line = false
# === Structs e Enums ===
# Vírgula final em structs (padrão: Always)
trailing_comma = "Vertical"
# Vírgula final em argumentos de macro
# trailing_comma_macro = "Always"
# === Match ===
# Braço de match em uma linha se couber (padrão: true)
match_arm_blocks = true
# Alinhar braços de match com => (instável)
# match_arm_leading_pipes = "Always"
# === Strings ===
# Formatar strings literais (padrão: false)
format_strings = false
# === Macros ===
# Formatar corpo de macros (padrão: true)
format_macro_matchers = false
# Formatar macros declarativas (padrão: true)
format_macro_bodies = true
# === Use declarations ===
# Reordenar imports (padrão: true)
reorder_imports = true
# Reordenar módulos (padrão: true)
reorder_modules = true
# === Misc ===
# Forçar multilinha para where clauses (padrão: false)
where_single_line = false
# Usar field init shorthand (padrão: false)
use_field_init_shorthand = true
# Usar try! vs ? (padrão: false)
use_try_shorthand = true
# Quebrar strings longas
format_strings = false
# Número de espaços antes de : em struct literal
space_before_colon = false
space_after_colon = true
Configurações Populares por Estilo
Estilo Minimalista (menos mudanças):
# rustfmt.toml - Minimalista
max_width = 100
tab_spaces = 4
edition = "2021"
Estilo Detalhado (projeto empresarial):
# rustfmt.toml - Empresarial
max_width = 100
tab_spaces = 4
edition = "2021"
group_imports = "StdExternalCrate"
imports_granularity = "Crate"
reorder_imports = true
reorder_modules = true
use_field_init_shorthand = true
use_try_shorthand = true
trailing_comma = "Vertical"
Estilo Compacto (linhas curtas):
# rustfmt.toml - Compacto
max_width = 80
tab_spaces = 2
edition = "2021"
fn_single_line = true
where_single_line = true
Configurações Instáveis (Nightly)
Algumas opções requerem a toolchain nightly:
# rustfmt.toml com opções instáveis
# Requer: cargo +nightly fmt
# Estabilidade
unstable_features = true
# Estilo de import
imports_granularity = "Module"
# Agrupar e reordenar imports
group_imports = "StdExternalCrate"
# Formatar doc comments
format_code_in_doc_comments = true
# Formatar strings
format_strings = true
# Alinhamento de campos de struct
struct_field_align_threshold = 20
# Estilo de enum
enum_discrim_align_threshold = 20
# Cor na saída
color = "Auto"
# Número máximo de linhas em branco consecutivas
blank_lines_upper_bound = 2
blank_lines_lower_bound = 0
# Estilo de closures
closure_block_indent_threshold = 5
Para usar opções instáveis:
# Formatar com nightly
cargo +nightly fmt
# Verificar com nightly
cargo +nightly fmt -- --check
Integração com Editores
VS Code
// .vscode/settings.json
{
// Formatar ao salvar
"editor.formatOnSave": true,
// Usar rustfmt como formatador
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnType": true
},
// Configurações do rust-analyzer
"rust-analyzer.rustfmt.extraArgs": [
"--config",
"max_width=100"
],
// Usar nightly rustfmt (para opções instáveis)
"rust-analyzer.rustfmt.overrideCommand": [
"rustup", "run", "nightly", "--",
"rustfmt", "--edition", "2021"
]
}
Neovim (com rust-analyzer)
-- init.lua ou lua/plugins/lsp.lua
local lspconfig = require('lspconfig')
lspconfig.rust_analyzer.setup({
settings = {
['rust-analyzer'] = {
rustfmt = {
-- Usar nightly rustfmt
overrideCommand = {
"rustup", "run", "nightly", "--",
"rustfmt", "--edition", "2021", "--"
},
extraArgs = { "--config", "max_width=100" },
},
},
},
})
-- Formatar ao salvar
vim.api.nvim_create_autocmd("BufWritePre", {
pattern = "*.rs",
callback = function()
vim.lsp.buf.format({ async = false })
end,
})
Vim (com vim-rustfmt)
" .vimrc
let g:rustfmt_autosave = 1
let g:rustfmt_emit_files = 1
let g:rustfmt_fail_silently = 0
let g:rustfmt_options = '--edition 2021'
" Atalho manual
nnoremap <leader>rf :RustFmt<CR>
IntelliJ / CLion
Configurações > Languages & Frameworks > Rust > Rustfmt
- Ativar "Run rustfmt on save"
- Ativar "Use rustfmt instead of built-in formatter"
- Adicionar caminho para rustfmt se necessário
Helix
# ~/.config/helix/languages.toml
[[language]]
name = "rust"
auto-format = true
formatter = { command = "rustfmt", args = ["--edition", "2021"] }
Boas Práticas
1. Use Configuração Padrão Sempre Que Possível
# rustfmt.toml - O mínimo necessário
edition = "2021"
# A maioria dos projetos Rust open-source usa as configurações padrão.
# Menos configuração = menos surpresas para contribuidores.
2. Configure Formatação no CI
# .github/workflows/fmt.yml
name: Formatação
on: [push, pull_request]
jobs:
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
3. Use Pre-commit Hooks
#!/bin/bash
# .git/hooks/pre-commit
echo "Verificando formatação..."
cargo fmt --all -- --check
if [ $? -ne 0 ]; then
echo ""
echo "Código não formatado! Execute 'cargo fmt' antes de commitar."
exit 1
fi
4. Documente Exceções com #[rustfmt::skip]
// Sempre explique por que está pulando formatação
#[rustfmt::skip]
// Manter formatação manual para alinhar colunas da tabela de lookup
const TABELA: &[(u8, &str, f64)] = &[
(0x00, "nulo", 0.0),
(0x01, "inicio", 1.0),
(0x02, "fim", 2.0),
(0xFF, "especial", 99.9),
];
5. Organize Imports Consistentemente
# rustfmt.toml
group_imports = "StdExternalCrate"
imports_granularity = "Crate"
reorder_imports = true
Resultado:
// Imports da biblioteca padrão
use std::collections::HashMap;
use std::io::{self, Read, Write};
// Imports de crates externas
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
// Imports do próprio crate
use crate::config::Config;
use crate::models::{Usuario, Produto};
Exemplos Práticos
Exemplo 1: Configuração Completa para Equipe
# rustfmt.toml - Configuração de equipe
edition = "2021"
max_width = 100
tab_spaces = 4
hard_tabs = false
# Imports organizados
reorder_imports = true
reorder_modules = true
group_imports = "StdExternalCrate"
imports_granularity = "Crate"
# Estilo de código
use_field_init_shorthand = true
use_try_shorthand = true
trailing_comma = "Vertical"
# Chaves
brace_style = "SameLineWhere"
control_brace_style = "AlwaysSameLine"
Exemplo 2: Antes e Depois - Código Real
Antes da formatação:
use serde::{Serialize,Deserialize};
use std::collections::HashMap;
use anyhow::Result;
use std::fs;
#[derive(Debug,Serialize,Deserialize)]
struct Config{
nome:String,
porta:u16,
hosts:Vec<String>,
opcoes:HashMap<String,String>,
}
impl Config{
fn carregar(caminho:&str)->Result<Self>{
let conteudo=fs::read_to_string(caminho)?;
let config:Config=serde_json::from_str(&conteudo)?;
Ok(config)
}
fn validar(&self)->Result<()>{
if self.nome.is_empty(){anyhow::bail!("Nome vazio")}
if self.porta==0{anyhow::bail!("Porta inválida")}
if self.hosts.is_empty(){anyhow::bail!("Sem hosts")}
Ok(())
}
fn hosts_formatados(&self)->String{
self.hosts.iter().enumerate().map(|(i,h)|format!("{}. {}",i+1,h)).collect::<Vec<_>>().join("\n")
}
}
Depois do cargo fmt:
use std::collections::HashMap;
use std::fs;
use anyhow::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Config {
nome: String,
porta: u16,
hosts: Vec<String>,
opcoes: HashMap<String, String>,
}
impl Config {
fn carregar(caminho: &str) -> Result<Self> {
let conteudo = fs::read_to_string(caminho)?;
let config: Config = serde_json::from_str(&conteudo)?;
Ok(config)
}
fn validar(&self) -> Result<()> {
if self.nome.is_empty() {
anyhow::bail!("Nome vazio")
}
if self.porta == 0 {
anyhow::bail!("Porta inválida")
}
if self.hosts.is_empty() {
anyhow::bail!("Sem hosts")
}
Ok(())
}
fn hosts_formatados(&self) -> String {
self.hosts
.iter()
.enumerate()
.map(|(i, h)| format!("{}. {}", i + 1, h))
.collect::<Vec<_>>()
.join("\n")
}
}
Exemplo 3: Script de Verificação Completo
#!/bin/bash
# scripts/verificar-codigo.sh
# Verificação completa de qualidade de código
set -euo pipefail
echo "=== Verificando formatação ==="
if ! cargo fmt --all -- --check; then
echo "FALHOU: Código não formatado. Execute 'cargo fmt --all'"
exit 1
fi
echo "OK: Formatação correta"
echo ""
echo "=== Executando Clippy ==="
if ! cargo clippy --workspace --all-targets --all-features -- -D warnings; then
echo "FALHOU: Clippy encontrou problemas"
exit 1
fi
echo "OK: Clippy aprovado"
echo ""
echo "=== Executando testes ==="
if ! cargo test --workspace --all-features; then
echo "FALHOU: Testes falharam"
exit 1
fi
echo "OK: Todos os testes passaram"
echo ""
echo "=== Verificação de documentação ==="
if ! RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --all-features; then
echo "FALHOU: Documentação com problemas"
exit 1
fi
echo "OK: Documentação gerada com sucesso"
echo ""
echo "=== Todas as verificações passaram! ==="
Exemplo 4: Makefile com Formatação
# Makefile
.PHONY: fmt fmt-check lint test check all
# Formatar código
fmt:
cargo fmt --all
# Verificar formatação
fmt-check:
cargo fmt --all -- --check
# Linting
lint:
cargo clippy --workspace --all-targets --all-features -- -D warnings
# Testes
test:
cargo test --workspace --all-features
# Verificação rápida (sem testes)
check: fmt-check lint
cargo check --workspace --all-features
# Verificação completa
all: fmt-check lint test
@echo "Todas as verificações passaram!"
Comparação com Alternativas
| Característica | Rustfmt | Prettier (JS) | Black (Python) | gofmt (Go) | clang-format (C++) |
|---|---|---|---|---|---|
| Oficial | Sim | Não | Não | Sim | Sim |
| Configurável | Moderado | Pouco | Mínimo | Nenhum | Muito |
| Opinionado | Moderado | Sim | Muito | Muito | Não |
| Estável | Sim | Sim | Sim | Sim | Sim |
| Velocidade | Rápido | Médio | Rápido | Muito rápido | Rápido |
| Editor support | Excelente | Excelente | Excelente | Excelente | Bom |
| Opções instáveis | Via nightly | N/A | N/A | N/A | N/A |
O Rustfmt ocupa um meio-termo saudável entre a rigidez total do gofmt (zero opções) e a flexibilidade excessiva do clang-format (centenas de opções). Ele oferece opções suficientes para acomodar preferências de equipe sem permitir configurações que resultem em código inconsistente entre projetos.
Conclusão
O Rustfmt é uma ferramenta essencial que todo projeto Rust deveria adotar desde o início. Ao automatizar a formatação de código, ele elimina discussões improdutivas sobre estilo e permite que a equipe foque no que realmente importa: a lógica e a arquitetura do software.
Pontos-chave para lembrar:
- Use as configurações padrão sempre que possível para maximizar consistência com a comunidade
- Configure o CI para verificar formatação com
cargo fmt -- --check - Integre com seu editor para formatar ao salvar
- Use
#[rustfmt::skip]com parcimônia e sempre documente o motivo group_importseimports_granularitysão as personalizações mais impactantes- Pre-commit hooks evitam commits com formatação incorreta
Para referência completa de todas as opções, consulte a documentação oficial do Rustfmt.
No próximo passo, conheça o Serde para serialização e desserialização de dados em Rust.