Variáveis, Tipos e a Arte da Imutabilidade
No artigo anterior, instalamos o Rust e entendemos por que ele existe. Hoje vamos escrever código de verdade — e já na primeira linha você vai se deparar com algo que surpreende quem vem de outras linguagens: variáveis em Rust são imutáveis por padrão.
Isso não é um descuido do designer da linguagem. É uma decisão filosófica profunda, e ao final deste artigo você vai entender por quê ela torna seu código mais seguro e mais fácil de raciocinar.
Declarando variáveis com let
Em Rust, você declara variáveis com a palavra-chave let:
fn main() {
let x = 5;
println!("O valor de x é: {x}");
}
Simples. Mas tente mudar o valor de x depois:
fn main() {
let x = 5;
x = 10; // ERRO: cannot assign twice to immutable variable
println!("{x}");
}
O compilador recusa. E mais importante: ele te diz exatamente o que fazer para corrigir. Essa é uma marca registrada de Rust — mensagens de erro que ensinam, não apenas acusam.
Quando você precisa mudar: mut
Para declarar uma variável mutável, você usa a palavra-chave mut:
fn main() {
let mut contador = 0;
println!("Antes: {contador}");
contador += 1;
println!("Depois: {contador}");
}
O mut é uma declaração de intenção. Quando você lê let x, sabe que esse valor não vai mudar. Quando lê let mut x, sabe imediatamente que esse valor será modificado em algum momento. Isso torna o código autodocumentado — uma leitura rápida já revela quais partes do estado do programa são dinâmicas.
Em equipes grandes, essa distinção evita horas de debugging.
Tipos primitivos
Rust é uma linguagem estaticamente tipada: todo valor tem um tipo conhecido em tempo de compilação. Na maioria dos casos, o compilador infere o tipo automaticamente — você não precisa escrevê-lo. Mas é fundamental conhecê-los.
Inteiros
Rust oferece inteiros de tamanhos explícitos, com e sem sinal:
fn main() {
let a: i32 = -42; // inteiro de 32 bits com sinal
let b: u32 = 42; // inteiro de 32 bits sem sinal
let c: i64 = 1_000_000; // underscore para legibilidade
let d: u8 = 255; // 0 a 255
println!("{a} {b} {c} {d}");
}
Os tipos disponíveis são: i8, i16, i32, i64, i128, isize (com sinal) e suas versões u (sem sinal). O tipo padrão, quando não especificado, é i32 — escolha segura e eficiente na maioria dos processadores modernos.
O isize e usize têm tamanho dependente da arquitetura (32 ou 64 bits) e são usados especialmente para índices de coleções.
Ponto flutuante
fn main() {
let pi: f64 = 3.14159265358979;
let altura: f32 = 1.75;
println!("PI ≈ {pi}, altura = {altura}");
}
f64 é o padrão e o mais comum — oferece precisão dupla. Use f32 apenas quando memória for uma restrição real.
Booleanos
fn main() {
let ativo: bool = true;
let deslogado = false; // tipo inferido
println!("{ativo} {deslogado}");
}
Caracteres
O tipo char em Rust é Unicode completo — não apenas ASCII. Ocupa 4 bytes:
fn main() {
let letra = 'A';
let emoji = '🦀'; // o caranguejo, mascote do Rust
let kanji = '日';
println!("{letra} {emoji} {kanji}");
}
Shadowing — redeclarar com propósito
Rust tem um recurso chamado shadowing: você pode redeclarar uma variável com let no mesmo escopo, e a nova declaração "sombrea" a anterior:
fn main() {
let x = 5;
let x = x + 1; // novo x, baseado no anterior
let x = x * 2; // novo x novamente
println!("x = {x}"); // imprime: x = 12
}
Isso parece mutabilidade, mas é diferente em dois aspectos importantes. Primeiro, cada let x cria uma nova variável — a anterior deixa de existir naquele escopo. Segundo, você pode mudar o tipo:
fn main() {
let texto = " 42 ";
let texto: i32 = texto.trim().parse().expect("Não é um número");
println!("{texto}"); // 42, agora um inteiro
}
Com mut, mudar o tipo seria um erro de compilação. Com shadowing, é natural. Use shadowing quando estiver transformando um valor em algo novo — semanticamente diferente do original.
Constantes
Além de variáveis, Rust tem constantes com const. Elas são sempre imutáveis, exigem anotação de tipo explícita, e existem durante toda a execução do programa:
const VELOCIDADE_DA_LUZ: u64 = 299_792_458; // metros por segundo
fn main() {
println!("c = {VELOCIDADE_DA_LUZ} m/s");
}
Por convenção, constantes são escritas em SNAKE_CASE_MAIÚSCULO. Elas podem ser declaradas em qualquer escopo, inclusive fora de funções — algo que let não permite.
Tipos compostos: tuplas e arrays
Tuplas
Tuplas agrupam valores de tipos diferentes em um único valor. Têm tamanho fixo:
fn main() {
let pessoa: (&str, u8, f64) = ("Ana", 30, 1.68);
let nome = pessoa.0;
let idade = pessoa.1;
let altura = pessoa.2;
println!("{nome} tem {idade} anos e {altura}m de altura.");
}
Você também pode desestruturar uma tupla diretamente:
fn main() {
let (nome, idade, altura) = ("Ana", 30, 1.68);
println!("{nome}, {idade}, {altura}");
}
Arrays
Arrays agrupam valores do mesmo tipo com tamanho fixo em tempo de compilação:
fn main() {
let dias = ["Seg", "Ter", "Qua", "Qui", "Sex"];
let primeiro = dias[0];
println!("Primeiro dia útil: {primeiro}");
let zeros = [0u32; 5]; // cinco zeros do tipo u32
println!("{:?}", zeros); // {:?} imprime arrays inteiros
}
Arrays em Rust não crescem. Para coleções dinâmicas, usaremos Vec<T> — mas isso é assunto para artigos futuros.
Um programa completo
Vamos reunir o que aprendemos em um exemplo coeso:
fn main() {
const TAXA_JUROS: f64 = 0.05; // 5% ao ano
let valor_inicial: f64 = 1_000.0;
let mut saldo = valor_inicial;
let anos = 3;
let mut ano_atual = 1;
while ano_atual <= anos {
saldo = saldo * (1.0 + TAXA_JUROS);
println!("Ano {ano_atual}: R$ {saldo:.2}");
ano_atual += 1;
}
let rendimento = saldo - valor_inicial;
println!("\nRendimento total: R$ {rendimento:.2}");
}
Saída esperada:
Ano 1: R$ 1050.00
Ano 2: R$ 1102.50
Ano 3: R$ 1157.63
Rendimento total: R$ 157.63
Note o {saldo:.2} — a formatação com duas casas decimais. Rust tem um sistema de formatação poderoso embutido na macro println!.
O que o compilador nos ensina
Se você escreveu código Rust hoje e o compilador reclamou, não desanime. Experimente propositalmente causar um erro — declare uma variável imutável e tente alterá-la. Leia a mensagem completa. Rust é provavelmente a linguagem com as mensagens de erro mais didáticas que existem. Cada erro vem com uma explicação e, frequentemente, com a correção sugerida.
Aprender a ler o compilador é parte essencial de aprender Rust.
Fontes e leituras recomendadas
- The Rust Programming Language, Cap. 3 — Common Programming Concepts — https://doc.rust-lang.org/book/ch03-00-common-programming-concepts.html
- Rust Reference — Types — especificação formal dos tipos primitivos — https://doc.rust-lang.org/reference/types.html
- Rust by Example — Primitives — exemplos interativos no navegador — https://doc.rust-lang.org/rust-by-example/primitives.html
- The Rust Reference — Const items — https://doc.rust-lang.org/reference/items/constant-items.html
- Rustlings — exercícios práticos para fixar os conceitos deste artigo — https://github.com/rust-lang/rustlings