Variáveis e Imutabilidade em Rust
Em Rust, toda variável é imutável por padrão. Isso é uma decisão arquitetônica fundamental que torna o código mais previsível e seguro. Quando você declara uma variável com let, ela não pode ser alterada após sua inicialização, a menos que você explicitamente a declare como mutável usando a palavra-chave mut.
fn main() {
let x = 5;
// x = 6; // ❌ Erro de compilação
let mut y = 5;
y = 6; // ✅ Válido
println!("y = {}", y); // Saída: y = 6
}
Esse padrão força o desenvolvedor a pensar sobre quais dados realmente precisam mudar durante a execução do programa. A imutabilidade por padrão reduz bugs relacionados a estado compartilhado e facilita o raciocínio sobre o fluxo de dados. Diferente de linguagens como Python ou JavaScript onde tudo é mutável automaticamente, Rust exige que você seja explícito sobre suas intenções.
Tipos Primitivos em Rust
Rust oferece um conjunto rico de tipos primitivos divididos em categorias principais. Os tipos inteiros incluem i8, i16, i32, i64, i128 (assinados) e u8, u16, u32, u64, u128 (sem sinal). O tipo padrão é i32. Os tipos de ponto flutuante são f32 e f64, ambos seguindo o padrão IEEE 754. Já o tipo bool representa valores booleanos (true ou false), e char representa um caractere Unicode de 4 bytes.
fn main() {
// Inteiros
let a: i32 = -42;
let b: u8 = 255;
let c = 100; // Inferido como i32
// Ponto flutuante
let pi: f64 = 3.14159;
let f = 2.5; // Inferido como f64
// Booleano
let ativo = true;
// Caractere (4 bytes, suporta Unicode)
let letra = 'A';
let emoji = '🦀';
println!("Inteiro: {}, Float: {}, Bool: {}, Char: {}", a, pi, ativo, emoji);
}
A inferência de tipos em Rust é poderosa: o compilador deduz o tipo baseado no contexto e na operação realizada. Mesmo sem anotações explícitas, o compilador garante type safety em tempo de compilação. Para tipos numéricos, você pode especificar literais com sufixos como 42i32 ou 3.14f32 para ser explícito.
### Operações e Comportamento
Rust não realiza conversão implícita entre tipos numéricos diferentes. Se você precisa converter i32 para i64, deve fazer explicitamente com as. Além disso, operações aritméticas podem causar overflow ou underflow — em modo debug, Rust causa panic; em release, usa aritmética com wrap-around.
fn main() {
let x: i32 = 10;
let y: i64 = x as i64; // Conversão explícita
let resultado = 5 + 3;
let div = 10 / 3; // Divisão inteira = 3
let resto = 10 % 3; // Resto = 1
println!("{}, {}, {}", resultado, div, resto);
}
Shadowing e Escopo de Variáveis
O shadowing permite redeclarar uma variável com o mesmo nome, "encobrindo" a anterior. Diferente de reatribuição mutável, o shadowing cria uma nova vinculação com potencialmente um tipo diferente. Isso é útil ao transformar dados em etapas sucessivas.
fn main() {
let x = 5; // x é i32
let x = x + 1; // Nova variável x agora é 6
{
let x = x * 2; // Escopo: x = 12
println!("Dentro do escopo: {}", x);
}
println!("Fora do escopo: {}", x); // x = 6
// Mudança de tipo via shadowing
let spaces = " ";
let spaces = spaces.len(); // spaces agora é usize
println!("Espaços: {}", spaces); // Saída: Espaços: 3
}
O escopo em Rust segue regras claras: uma variável existe desde sua declaração até o final do bloco {} contendo-a. Variáveis internas sobrescrevem as externas no mesmo namespace. Essa separação clara de escopos evita bugs sutis de estado global e torna o código mais modular e fácil de raciocinar.
Tuplas e Arrays como Tipos Compostos
Para além dos primitivos escalares, Rust oferece tuplas e arrays como tipos compostos. Uma tupla agrupa valores de tipos potencialmente diferentes em uma estrutura fixa. Arrays, por sua vez, armazenam múltiplos valores do mesmo tipo com tamanho fixo em tempo de compilação.
fn main() {
// Tupla heterogênea
let pessoa: (String, i32, bool) = ("Ana".to_string(), 28, true);
println!("Nome: {}, Idade: {}, Ativo: {}", pessoa.0, pessoa.1, pessoa.2);
// Destructuring de tupla
let (nome, idade, _) = pessoa;
println!("Nome: {}, Idade: {}", nome, idade);
// Array homogêneo (tamanho fixo)
let numeros: [i32; 5] = [1, 2, 3, 4, 5];
let primeiro = numeros[0]; // Acesso indexado
// Array com valor repetido
let zeros = [0; 10]; // Dez zeros
println!("Primeiro número: {}, Zeros: {:?}", primeiro, &zeros[0..3]);
}
Tuplas são ideais para retornar múltiplos valores de uma função. Arrays são úteis quando você precisa de coleções de tamanho conhecido em tempo de compilação. O destructuring facilita extrair valores sem acessar índices manualmente, tornando o código mais legível e seguro contra erros de indexação.
Conclusão
Três pilares fundamentais definem o modelo de variáveis em Rust: imutabilidade por padrão força você a ser explícito sobre mudanças, type safety em tempo de compilação previne classes inteiras de bugs, e tipos primitivos bem-definidos oferecem controle fino sobre memória e performance. Dominar esses conceitos é essencial para escrever código Rust eficaz e idiomático. A combinação de imutabilidade, tipagem estática e inferência cria um ambiente onde o compilador trabalha com você, não contra você, para produzir software seguro e eficiente.