Introdução: Por que Código Limpo em Rust Não É Apenas Estilo
Rust é uma linguagem que combina o desempenho do C++ com segurança de memória, mas seu sistema único de ownership e borrowing exige uma abordagem especial para escrever código. Os princípios de Código Limpo formulados por Robert Martin ganham uma nova dimensão em Rust: aqui, a limpeza do código está diretamente ligada à correção, desempenho e facilidade de manutenção.
Neste artigo, exploraremos como aplicar expressões idiomáticas do Rust para criar código autodocumentado, seguro e eficiente. Você verá como substituir erros típicos de iniciantes por soluções elegantes aceitas pela comunidade Rust.
1. Tratamento de Erros Idiomático: De unwrap() a Padrões Expressivos
Um dos primeiros problemas que iniciantes enfrentam é o uso excessivo de unwrap(). Em Código Limpo, um erro não é uma exceção, mas parte da arquitetura. O Rust fornece tipos poderosos Option e Result para tratamento explícito de casos extremos.
Exemplo Ruim (Código Sujo)
fn parse_number(input: &str) -> i32 { input.parse::<i32>().unwrap()}
fn main() { let val = parse_number("not a number"); println!("{}", val); // Pânico!}Exemplo Bom (Código Limpo)
use std::num::ParseIntError;
fn parse_number(input: &str) -> Result<i32, ParseIntError> { input.parse::<i32>()}
fn main() { match parse_number("42") { Ok(n) => println!("Número: {}", n), Err(e) => eprintln!("Erro: {}", e), }}Por que isso é Código Limpo? A função declara explicitamente que pode falhar. O código chamador decide como lidar com a falha — registrar no log, tentar novamente ou propagar o erro para cima.
Técnica Avançada: O Operador ?
fn read_config(path: &str) -> Result<String, std::io::Error> { let content = std::fs::read_to_string(path)?; Ok(content)}O operador ? é uma substituição elegante para expressões match prolixas. Ele retorna o erro para cima se o resultado for Err, e extrai o valor se for Ok. Isso reduz o aninhamento e melhora a legibilidade.
2. Ownership e Borrowing: Projete a Arquitetura, Não Lute Contra o Compilador
O sistema de ownership do Rust pode parecer uma limitação, mas na verdade ajuda a escrever código limpo. Os problemas começam quando um desenvolvedor tenta "enganar" o compilador com clone() ou Rc<RefCell> sem necessidade.
Antipadrão: Clonagem Excessiva
fn process_data(data: Vec<String>) { for item in data.clone() { // Por que clonar? println!("{}", item); } // data não é mais usada}Solução Limpa: Borrowing por Referência
fn process_data(data: &[String]) { for item in data { println!("{}", item); }}
fn main() { let my_data = vec!["a".to_string(), "b".to_string()]; process_data(&my_data); // A ownership permanece no main println!("Vetor ainda está acessível: {:?}", my_data);}Princípio de Código Limpo: Escopo mínimo para mutações. Se uma função não deve modificar dados — aceite uma referência imutável &T. Se for necessária modificação — &mut T. Isso torna o código previsível e seguro.
Expressão Idiomática: Passagem de Ownership para Transformação
fn to_uppercase(mut text: String) -> String { text.make_ascii_uppercase(); text // A ownership é retornada}Aqui, a função "consome" a string, modifica-a e a retorna. Isso é eficiente (sem cópia) e explícito: quem chama entende que a variável original não é mais válida.