Introdução: por que Rust parece difícil, mas vale a pena
Rust é uma linguagem que promete gerenciamento seguro de memória sem coletor de lixo e alto desempenho. No entanto, são exatamente essas vantagens que se tornam um obstáculo para iniciantes. O compilador Rust é rigoroso nos mínimos detalhes: ele não perdoa comportamento indefinido, ponteiros soltos ou condições de corrida. Neste artigo, vamos analisar os principais erros de desenvolvedores Rust iniciantes — desde os mais comuns até os mais traiçoeiros. Você aprenderá como evitá-los e acelerar seu caminho para a maestria.
1. A luta com o Borrow Checker
A "dor" mais conhecida dos iniciantes é o sistema de ownership e borrowing. O compilador não permite que você crie uma referência a dados que possam ser alterados ou excluídos.
1.1. Tentativa de modificar dados através de uma referência imutável
Um dos primeiros erros é tentar modificar uma variável que já possui uma referência imutável (&).
let mut x = 5;let y = &x; // referência imutávellet z = &mut x; // ERRO: não é possível criar uma referência mutável enquanto existir uma imutávelprintln!("{}", y);Como corrigir: Certifique-se de que o escopo das referências imutáveis terminou antes de criar uma mutável. Use chaves para delimitar o escopo.
let mut x = 5;{ let y = &x; // escopo de y é limitado println!("{}", y);}let z = &mut x; // agora é possível1.2. Retornar referência a uma variável local
Iniciantes frequentemente tentam retornar uma referência a dados criados dentro de uma função. Isso leva ao erro "dangling reference".
fn get_ref() -> &String { let s = String::from("hello"); &s // ERRO: s será deletada após sair da função}Como corrigir: Retorne a ownership (a própria string) ou use um tempo de vida estático ('static), se os dados viverem para sempre.
fn get_string() -> String { let s = String::from("hello"); s // transfere a ownership}2. Uso incorreto de lifetimes
Quando você começa a trabalhar com funções que aceitam múltiplas referências, o compilador exige que você especifique como elas se relacionam em termos de tempo de vida. Iniciantes ou ignoram lifetimes ou escrevem 'static em todos os lugares onde não é necessário.
2.1. Omissão de anotações de lifetime em assinaturas
Se uma função aceita duas referências e retorna uma, o Rust não consegue determinar sozinho qual delas "vive" mais tempo.
fn longest(x: &str, y: &str) -> &str { // ERRO: precisa de lifetime if x.len() > y.len() { x } else { y }}Como corrigir: Adicione um parâmetro de lifetime genérico.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}2.2. Uso excessivo de 'static
'static significa que a referência vive por todo o programa. Usá-lo para literais de string é ok, mas para dados dinâmicos é uma má prática.
fn get_static() -> &'static str { "hello" // correto}
fn get_dynamic() -> &'static str { let s = String::from("hello"); &s // ERRO: s não vive para sempre}Dica: Tente não usar 'static sem necessidade. É melhor transferir a ownership ou usar Arc<Mutex<T>> para concorrência.
3. Abuso de unwrap() e pânico
Rust incentiva o tratamento explícito de erros através de Result e Option. Mas iniciantes frequentemente usam .unwrap() ou .expect() em todos os lugares para "calar" o compilador. Isso leva a pânico em tempo de execução.