Variáveis e tipos básicos
por Sloane KensingtonEntenda o conceito
Em Rust, variáveis são criadas com let e são imutáveis por padrão: depois de receber um valor, ele não muda. Para permitir novas atribuições, declare com let mut. Essa escolha deixa claro, já na declaração, quais valores vão mudar ao longo do programa.
Rust é uma linguagem de tipagem estática: cada variável tem um tipo conhecido em tempo de compilação. Na maioria das vezes o compilador infere o tipo pelo valor (let x = 5; vira i32), mas você pode anotá-lo: let nivel: u8 = 12;. Os tipos escalares são os inteiros com sinal (i8 a i128) e sem sinal (u8 a u128), os de ponto flutuante (f32 e f64), o bool e o char, que guarda um caractere Unicode entre aspas simples.
Rust não converte tipos numéricos sozinho: somar um u8 com um u32 é erro de compilação. A conversão é explícita, com as. Outro recurso importante é o sombreamento (shadowing): um novo let com o mesmo nome cria outra variável, que pode ter até um tipo diferente. Já as constantes usam const, nome em maiúsculas e tipo sempre anotado.
letcria uma variável imutável;let mutpermite mudar o valor depois, mas nunca o tipo.- Sem anotação, inteiros viram
i32e decimais viramf64; anote (let x: u8 = 7;) quando precisar de outro tipo. - Tipos
iaceitam negativos e tiposunão; cada um tem limites, comou8::MAX(255). - Rust não mistura tipos numéricos numa conta: converta com
as(qtd as f64), sabendo queascorta casas decimais e pode truncar valores. - Sombreamento (
let preco = preco * 0.9;) cria uma variável nova com o mesmo nome;const LIMITE: u32 = 5;define um valor fixo com tipo obrigatório.
Do conceito à prática
3 exemplos para ver a ideia funcionando.
01Estoque da loja com let, mut e sombreamento
// Variáveis com let: imutáveis por padrão, mutáveis com mut.
fn main() {
// Sem mut, o valor não pode mudar depois de definido.
let produto = "Caderno universitário";
let preco = 24.90;
// Com mut, a variável aceita novas atribuições.
let mut estoque = 50;
println!("{produto}: R$ {preco:.2}, {estoque} em estoque");
// Chegou uma remessa e saíram algumas vendas.
estoque += 30;
estoque -= 12;
println!("Depois do dia: {estoque} unidades");
// Sombreamento: um novo let com o mesmo nome cria outra variável.
let preco = preco * 0.9;
println!("Com 10% de desconto: R$ {preco:.2}");
// O sombreamento pode até trocar o tipo; mut não pode.
let estoque = estoque > 0;
println!("Disponível para venda? {estoque}");
// Constantes: nome em maiúsculas e tipo sempre explícito.
const LIMITE_POR_CLIENTE: u32 = 5;
println!("Limite por cliente: {LIMITE_POR_CLIENTE} unidades");
}
O exemplo mostra as três formas de lidar com valores: produto e preco são imutáveis, estoque é declarado com mut porque muda com remessas e vendas, e LIMITE_POR_CLIENTE é uma constante. Os operadores += e -= só funcionam porque estoque é mutável.
O sombreamento aparece duas vezes: let preco = preco * 0.9; cria um novo preço com desconto, e let estoque = estoque > 0; troca o tipo de inteiro para bool. Isso seria impossível só com mut, porque uma variável mutável mantém sempre o mesmo tipo.
02Ficha do personagem com os tipos escalares
// Os tipos escalares de Rust: inteiros, ponto flutuante, bool e char.
fn main() {
// Inteiro com sinal: i32 é o padrão quando não há anotação.
let pontos = -150;
// Inteiro sem sinal de 8 bits: vai de 0 a 255.
let nivel: u8 = 12;
// u64 para valores grandes; o _ só separa os dígitos.
let ouro: u64 = 1_250_000;
// Ponto flutuante: f64 é o padrão.
let velocidade = 3.5;
let precisao: f32 = 0.875;
// bool só aceita true ou false.
let tem_escudo = true;
// char usa aspas simples e guarda um caractere Unicode.
let classe = 'M';
let simbolo = '★';
println!("Pontos: {pontos}, nível: {nivel}, ouro: {ouro}");
println!("Velocidade: {velocidade}, precisão: {precisao}");
println!("Escudo: {tem_escudo}, classe: {classe} {simbolo}");
// Cada tipo inteiro tem limites conhecidos.
println!("u8 vai de {} a {}", u8::MIN, u8::MAX);
println!("i32 vai de {} a {}", i32::MIN, i32::MAX);
// Um char ocupa 4 bytes, mesmo sendo uma letra simples.
println!("Tamanho de um char: {} bytes", std::mem::size_of::<char>());
}
A ficha reúne os quatro grupos de tipos escalares: inteiros com e sem sinal (i32, u8, u64), ponto flutuante (f64, f32), bool e char. Repare que pontos não tem anotação e vira i32, e que o _ em 1_250_000 só facilita a leitura do número.
As constantes u8::MIN, u8::MAX, i32::MIN e i32::MAX mostram os limites de cada tipo, e std::mem::size_of::<char>() revela que um char ocupa 4 bytes: ele guarda qualquer caractere Unicode, como ★, não só letras do alfabeto inglês.
03Receita escalonada com conversões entre tipos
// Operações entre tipos: divisão inteira, resto e conversão com as.
fn main() {
const PORCOES_ORIGINAIS: u32 = 4;
let farinha_g: u32 = 500;
let ovos: u32 = 3;
let porcoes_desejadas: u32 = 6;
// Divisão entre inteiros descarta a parte decimal.
println!("Ovos por porção (inteiro): {}", ovos / PORCOES_ORIGINAIS);
// O resto da divisão usa %.
println!("Ovos que sobram: {}", ovos % PORCOES_ORIGINAIS);
// Rust não mistura tipos: converta com as antes da conta.
let fator = porcoes_desejadas as f64 / PORCOES_ORIGINAIS as f64;
println!("Fator da receita: {fator}");
let farinha_nova = farinha_g as f64 * fator;
let ovos_novos = ovos as f64 * fator;
println!("Farinha: {farinha_nova} g");
println!("Ovos (exato): {ovos_novos}");
// as de f64 para inteiro corta as casas decimais (não arredonda).
println!("Ovos com as u32: {}", ovos_novos as u32);
// Para arredondar para cima, use ceil() antes de converter.
println!("Ovos arredondados para cima: {}", ovos_novos.ceil() as u32);
// Conversão de inteiro grande para pequeno pode perder informação.
let calorias: u32 = 300;
println!("300 convertido para u8: {}", calorias as u8);
// Tuplas agrupam valores de tipos diferentes.
let (nome, minutos): (&str, u32) = ("Bolo de cenoura", 95);
println!("{nome}: {}h{:02}min", minutos / 60, minutos % 60);
}
Para ajustar uma receita de 4 para 6 porções, o programa precisa de contas com decimais, mas os dados são inteiros. Como Rust não mistura tipos, cada valor é convertido com as f64 antes da divisão. Antes disso, ovos / PORCOES_ORIGINAIS mostra que a divisão entre inteiros descarta a fração (dá 0) e % devolve o resto.
O exemplo também mostra os cuidados com as: de f64 para u32 ele corta as casas decimais (4.5 vira 4), por isso ceil() vem antes quando é preciso arredondar para cima; e de u32 para u8 ele trunca valores grandes (300 vira 44). No fim, uma tupla com tipos diferentes é desmontada em duas variáveis com um único let.
Agora é com você
Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.
01Ficha de um livro do acervoFácil
Crie a ficha de um livro da biblioteca guardando cada informação numa variável com o tipo anotado de forma explícita e imprima uma informação por linha, como na saída esperada.
- Há seis variáveis imutáveis, criadas com
lete com o tipo anotado:&str,u16,u32,f64,boolechar. - O ano usa
u16, as páginas usamu32e a estante é umchar. - Os valores aparecem no texto pelo nome da variável dentro das chaves, como
{titulo}. - A saída é idêntica à esperada.
fn main() {
let titulo: &str = "Dom Casmurro";
// declare ano (u16), paginas (u32), nota_media (f64),
// disponivel (bool) e estante (char)
println!("Título: {titulo}");
// imprima as demais informações
}
Título: Dom Casmurro
Ano: 1899
Páginas: 256
Nota média: 4.7
Disponível: true
Estante: C
Ver dica
Pense em qual tipo combina com cada dado: algum deles pode ser negativo? Tem casas decimais? É um único caractere?
Ver resposta
fn main() {
// Cada variável com o tipo anotado de forma explícita
let titulo: &str = "Dom Casmurro";
let ano: u16 = 1899;
let paginas: u32 = 256;
let nota_media: f64 = 4.7;
let disponivel: bool = true;
let estante: char = 'C';
println!("Título: {titulo}");
println!("Ano: {ano}");
println!("Páginas: {paginas}");
println!("Nota média: {nota_media}");
println!("Disponível: {disponivel}");
println!("Estante: {estante}");
}
Passo a passo
let titulo: &str = "Dom Casmurro";anota o tipo depois dos dois-pontos;&stré o tipo de um texto literal escrito entre aspas duplas.anousau16(0 a 65.535), suficiente para um ano e sem sinal, porque um ano de publicação não é negativo.paginasusau32, o inteiro sem sinal de 32 bits; quantidades que nunca são negativas combinam com tiposu.nota_media: f64guarda um número com casas decimais;f64é o tipo de ponto flutuante padrão em Rust.disponivel: boolsó aceitatrueoufalse, eestante: charguarda uma única letra entre aspas simples.- Cada
println!usa o nome da variável direto dentro das chaves ({titulo}), o que deixa o texto fácil de ler.
Por que funciona
Em Rust, let nome: Tipo = valor; cria uma variável imutável com o tipo indicado. O compilador confere se o valor cabe no tipo: 1899 cabe em u16, 4.7 é um f64 e 'C' é um char. Como nenhuma variável muda depois de criada, nenhuma precisa de mut.
Erros comuns
- Aspas trocadas:
let estante: char = "C";não compila (expected char, found &str). Um caractere usa aspas simples:'C'. - Inteiro num tipo decimal:
let nota_media: f64 = 4;não compila (expected f64, found integer). Escreva4.0. - Valor que não cabe:
let ano: u8 = 1899;falha comliteral out of range for u8, porqueu8vai só até 255. - Valor negativo em tipo sem sinal:
let paginas: u32 = -256;não compila (cannot apply unary operator -). Use um tipoiquando o valor puder ser negativo.
Outra forma de resolver
Sem as anotações (let ano = 1899;), o compilador infere i32 para os inteiros e f64 para os decimais, e a saída seria a mesma. Anotar é preferível quando você quer um tipo específico, como u16, ou quando quer deixar a intenção clara para quem lê.
Saída esperada
Título: Dom Casmurro
Ano: 1899
Páginas: 256
Nota média: 4.7
Disponível: true
Estante: C
02Pontos de vida na batalhaMédio
Acompanhe os pontos de vida de um herói durante uma batalha: ele começa com 100, perde 35 para um goblin, recupera 20 com uma poção e perde 90 para um dragão. No fim, use sombreamento para impedir vida negativa e para saber se o herói continua vivo.
- A vida é uma única variável
let mutdo tipoi32, alterada com-=e+=. - A vida final é obtida por sombreamento (
let vida = ...), limitando o valor a no mínimo 0. - Um segundo sombreamento transforma
vidanumboolque indica se o herói está vivo. - A saída é idêntica à esperada, com uma linha depois de cada etapa.
fn main() {
let mut vida: i32 = 100;
println!("Vida inicial: {vida}");
// goblin (-35), poção (+20) e dragão (-90)
// sombreie vida para não ficar negativa e depois para um bool
}
Vida inicial: 100
Após o goblin: 65
Após a poção: 85
Após o dragão: -5
Vida final: 0
Herói vivo? false
Ver dica
Uma variável mut nunca muda de tipo; para trocar de i32 para bool, você precisa de um novo let. Os inteiros têm um método que devolve o maior entre dois valores.
Ver resposta
fn main() {
// mut: a vida muda ao longo da batalha
let mut vida: i32 = 100;
println!("Vida inicial: {vida}");
vida -= 35; // ataque do goblin
println!("Após o goblin: {vida}");
vida += 20; // poção de cura
println!("Após a poção: {vida}");
vida -= 90; // sopro do dragão
println!("Após o dragão: {vida}");
// Sombreamento: nova variável imutável, sem valores negativos
let vida = vida.max(0);
println!("Vida final: {vida}");
// Sombreamento com troca de tipo: de i32 para bool
let vida = vida > 0;
println!("Herói vivo? {vida}");
}
Passo a passo
let mut vida: i32 = 100;cria uma variável mutável do tipoi32; omutpermite mudar o valor depois, ei32aceita negativos.vida -= 35;é a forma curta devida = vida - 35;; o mesmo vale para+=na poção.- Depois do dragão a vida fica em
-5, por isso o tipo precisava ter sinal: numu32, essa subtração causaria pânico em modo debug. let vida = vida.max(0);cria uma nova variável imutável com o mesmo nome;max(0)devolve o maior entre o valor e zero.let vida = vida > 0;sombreia de novo, agora trocando o tipo: a comparação gera umbool.- Cada
println!mostra o valor da variável visível naquele ponto do código.
Por que funciona
mut serve para o mesmo valor mudar ao longo do tempo, como a vida durante a batalha. O sombreamento é diferente: cada let cria uma variável nova que esconde a anterior. Por isso ele pode trocar o tipo (de i32 para bool) e deixar o resultado imutável, o que evita alterações acidentais depois do cálculo final.
Erros comuns
- Esquecer o
mut:let vida = 100;seguido devida -= 35;não compila (cannot assign twice to immutable variable). Declare comlet mut. - Trocar o tipo sem
let:vida = vida > 0;falha comexpected i32, found bool. Uma variávelmutmantém o tipo; para trocar, sombreie comlet. - Tipo sem sinal: com
let mut vida: u32 = 100;, a conta85 - 90geraattempt to subtract with overflowem modo debug. Usei32.
Outra forma de resolver
Em vez de max(0), dá para usar uma expressão if: let vida = if vida < 0 { 0 } else { vida };. Ela é preferível quando a regra tem mais de um caso, mas para limitar um valor a um mínimo o max é mais curto e direto.
Saída esperada
Vida inicial: 100
Após o goblin: 65
Após a poção: 85
Após o dragão: -5
Vida final: 0
Herói vivo? false
03Fechamento do carrinho em centavosDesafio
Feche o carrinho de uma papelaria guardando os preços em centavos (u32) e as quantidades em u8. Calcule subtotal, desconto de 10%, total, parcelamento em 3 vezes (o resto da divisão vai para a primeira parcela) e a média de preço por item.
- Desconto e número de parcelas são constantes
constdo tipou32; os preços sãou32em centavos e as quantidades sãou8convertidas comas u32. - Os valores em reais são impressos com
/ 100e% 100, com os centavos sempre em dois dígitos. - A parcela usa divisão inteira, e o resto (
%) é somado só à primeira parcela. - A média por item é calculada em
f64, convertendo os operandos comas f64, e impressa com duas casas.
const DESCONTO_PERCENTUAL: u32 = 10;
const PARCELAS: u32 = 3;
fn main() {
// caneta: 350 centavos x 4; caderno: 2490 x 2; mochila: 12995 x 1
let preco_caneta: u32 = 350;
let qtd_caneta: u8 = 4;
// calcule subtotal, desconto, total, parcelas e média
}
Subtotal: R$ 193,75
Desconto: R$ 19,37
Total: R$ 174,38
1ª parcela: R$ 58,14
Demais parcelas: R$ 58,12
Itens: 7, média por item: R$ 24.91
Ver dica
Multiplique antes de dividir para não perder centavos, e lembre que total é sempre igual a parcela * PARCELAS + resto.
Ver resposta
// Constante com tipo explícito, visível em todo o programa
const DESCONTO_PERCENTUAL: u32 = 10;
const PARCELAS: u32 = 3;
fn main() {
// Preços em centavos (u32) e quantidades em u8
let preco_caneta: u32 = 350;
let qtd_caneta: u8 = 4;
let preco_caderno: u32 = 2490;
let qtd_caderno: u8 = 2;
let preco_mochila: u32 = 12995;
let qtd_mochila: u8 = 1;
// u8 precisa virar u32 antes de multiplicar com o preço
let subtotal = preco_caneta * qtd_caneta as u32
+ preco_caderno * qtd_caderno as u32
+ preco_mochila * qtd_mochila as u32;
let desconto = subtotal * DESCONTO_PERCENTUAL / 100;
let total = subtotal - desconto;
// Reais com / 100 e centavos com % 100, sempre com dois dígitos
println!("Subtotal: R$ {},{:02}", subtotal / 100, subtotal % 100);
println!("Desconto: R$ {},{:02}", desconto / 100, desconto % 100);
println!("Total: R$ {},{:02}", total / 100, total % 100);
// Divisão inteira das parcelas; o resto vai para a primeira
let parcela = total / PARCELAS;
let primeira = parcela + total % PARCELAS;
println!("1ª parcela: R$ {},{:02}", primeira / 100, primeira % 100);
println!("Demais parcelas: R$ {},{:02}", parcela / 100, parcela % 100);
// Média por item em f64: converta os dois lados antes de dividir
let itens = qtd_caneta as u32 + qtd_caderno as u32 + qtd_mochila as u32;
let media = total as f64 / itens as f64 / 100.0;
println!("Itens: {itens}, média por item: R$ {media:.2}");
}
Passo a passo
const DESCONTO_PERCENTUAL: u32 = 10;econst PARCELAS: u32 = 3;ficam fora demain, em maiúsculas e com o tipo obrigatório; são valores fixos que o programa inteiro pode usar.- Os preços ficam em centavos (
u32), não em reais comf64: contas com inteiros são exatas, enquanto0.1 + 0.2em ponto flutuante não dá exatamente0.3. - As quantidades são
u8, e Rust não multiplicau32poru8; por isso cada uma virau32comqtd_caneta as u32antes da conta. subtotal * DESCONTO_PERCENTUAL / 100multiplica primeiro e divide depois; a divisão inteira descarta a fração e o desconto fica em 1937 centavos.total / 100dá os reais etotal % 100dá os centavos;{:02}garante dois dígitos, para que 5 centavos apareçam como05.total / PARCELASé a divisão inteira de cada parcela, etotal % PARCELASé o que sobra (2 centavos), somado só à primeira.- Para a média, os dois lados da divisão viram
f64comas f64; dividir17438por7como inteiros daria 2491 e perderia a fração antes do arredondamento.
Por que funciona
Rust nunca converte tipos numéricos sozinho: cada operação exige os dois lados do mesmo tipo, e a conversão com as fica visível no código. Assim, a solução controla onde a conta é inteira (preços, desconto, parcelas, em que o arredondamento para baixo é desejado) e onde é decimal (a média, formatada com {media:.2}). Divisão inteira e resto trabalham juntos: total == parcela * 3 + resto, e nenhum centavo se perde.
Erros comuns
- Misturar tipos:
preco_caneta * qtd_canetanão compila (mismatched typesecannot multiply u32 by u8). Converta comqtd_caneta as u32. - Dividir antes de multiplicar:
subtotal / 100 * DESCONTO_PERCENTUALdá 1930 em vez de 1937, porque a divisão inteira joga fora os centavos cedo demais. - Converter só o resultado:
(total / itens) as f64faz a divisão inteira primeiro e a média sai24.91por acaso, mas com outros valores perde a fração. Converta cada operando:total as f64 / itens as f64. - Esquecer o
{:02}: com{}nos centavos, R$ 58,05 apareceria comoR$ 58,5. Use sempre{:02}.
Outra forma de resolver
Para converter sem risco de perder valores, existe u32::from(qtd_caneta), que só compila quando a conversão nunca perde informação (de u8 para u32, por exemplo). Ele é preferível em código de produção, porque as também aceita conversões que cortam valores, como 300 as u8, sem avisar.
Saída esperada
Subtotal: R$ 193,75
Desconto: R$ 19,37
Total: R$ 174,38
1ª parcela: R$ 58,14
Demais parcelas: R$ 58,12
Itens: 7, média por item: R$ 24.91