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