Tipos escalares
Esta aula aborda os tipos escalares em Rust: inteiros (com atenção ao overflow), ponto flutuante, booleanos e caracteres (char e Unicode). Inclui exemplos práticos, boas práticas e exercícios com respostas.
Em Rust, tipos escalares representam um único valor. Eles são os blocos fundamentais para construir dados mais complexos. Nesta aula, exploraremos os quatro tipos escalares primários: inteiros, ponto flutuante, booleanos e caracteres. Cada um tem características específicas de tamanho, sinal e comportamento, especialmente em relação a operações aritméticas e representação de dados.
Entender esses tipos é crucial para escrever código seguro e eficiente, pois Rust é uma linguagem fortemente tipada que oferece controle refinado sobre a memória e prevenção de erros comuns, como overflow.
Inteiros e overflow
Inteiros em Rust podem ser com sinal (i8, i16, i32, i64, i128, isize) ou sem sinal (u8, u16, u32, u64, u128, usize). O número indica a quantidade de bits. Por exemplo, i32 ocupa 32 bits e pode representar valores de -2^31 a 2^31-1. O tipo isize depende da arquitetura da máquina (32 ou 64 bits).
Overflow ocorre quando o resultado de uma operação ultrapassa os limites do tipo. Em compilação debug, Rust detecta overflow e causa pânico (panic). Em release, o comportamento é de wrapping (envoltório), ou seja, o valor é truncado. Para lidar com overflow explicitamente, existem métodos como wrapping_add, checked_add, overflowing_add e saturating_add.
fn main() {
let x: u8 = 255;
// Em debug, isso causa pânico:
// let y = x + 1;
// Wrapping: 255 + 1 = 0
let y = x.wrapping_add(1);
println!("wrapping_add: {}", y); // 0
// Checked: retorna Option
match x.checked_add(1) {
Some(val) => println!("checked: {}", val),
None => println!("overflow!")
}
// Overflowing: retorna (resultado, indicador de overflow)
let (result, overflowed) = x.overflowing_add(1);
println!("overflowing: {} (overflow: {})", result, overflowed);
// Saturating: satura no valor máximo
let z = x.saturating_add(1);
println!("saturating: {}", z); // 255
}Ponto flutuante
Rust possui dois tipos de ponto flutuante: f32 (precisão simples) e f64 (precisão dupla). O padrão é f64, pois oferece maior precisão e desempenho similar em CPUs modernas. Eles seguem o padrão IEEE 754.
Operações com ponto flutuante podem resultar em valores especiais como infinito (INFINITY) e NaN (Not a Number). Comparações com NaN sempre retornam false, exceto quando usando o método is_nan().
fn main() {
let a: f64 = 2.0;
let b: f32 = 3.0;
let soma = a + b as f64; // precisa converter
println!("soma: {}", soma);
let inf = 1.0 / 0.0;
println!("infinito: {}", inf); // inf
let nan = 0.0 / 0.0;
println!("NaN: {}", nan); // NaN
println!("NaN == NaN? {}", nan == nan); // false
println!("is_nan? {}", nan.is_nan()); // true
}Booleano
O tipo booleano em Rust é bool, com valores true e false. Ocupa 1 byte. É usado em condições, loops e expressões lógicas. Conversão para inteiro não é automática; deve-se usar as keyword as (ex: true as u8 retorna 1).
fn main() {
let ativo: bool = true;
let desligado = false;
if ativo {
println!("Ativo!");
}
// Conversão para inteiro
let numero = ativo as u8;
println!("true como u8: {}", numero); // 1
// Operadores lógicos
let e = ativo && desligado; // false
let ou = ativo || desligado; // true
let nao = !ativo; // false
}Caractere (char e Unicode)
O tipo char em Rust representa um caractere Unicode de 4 bytes (32 bits). Diferente de outras linguagens onde char é ASCII, Rust suporta qualquer caractere Unicode, incluindo emojis e caracteres acentuados. Literais char são escritos entre aspas simples: 'a', 'ç', '😊'.
Internamente, char é um valor numérico de 0 a 0x10FFFF. Pode-se converter para u32 e vice-versa, desde que o valor seja válido. A função char::from_u32 retorna Option
fn main() {
let letra: char = 'R';
let emoji: char = '🚀';
let acento: char = 'ã';
println!("Letra: {}", letra);
println!("Emoji: {}", emoji);
println!("Acento: {}", acento);
// Conversão para u32
let codigo = letra as u32;
println!("Código Unicode de 'R': {}", codigo); // 82
// De u32 para char
let val: u32 = 0x1F680; // 🚀
match char::from_u32(val) {
Some(c) => println!("Caractere: {}", c),
None => println!("Valor inválido")
}
// Tamanho em bytes
println!("Tamanho de char: {} bytes", std::mem::size_of::()); // 4
} Boas práticas
- Prefira tipos inteiros com sinal (i32) para valores gerais, a menos que precise de otimização ou representação específica.
- Use usize para indexação de coleções e tamanhos.
- Para ponto flutuante, f64 é padrão e suficiente na maioria dos casos; use f32 apenas se precisar economizar memória ou em computação gráfica.
- Evite comparar floats diretamente com ==; use uma margem de erro (epsilon).
- Char é ideal para caracteres Unicode; para processamento de texto, use &str ou String.
Referências
- The Rust Programming Language - Data Types
- Rust Standard Library - i32
- Rust Standard Library - f64
- Rust Standard Library - bool
- Rust Standard Library - char
- Rust Reference - Numeric types
Exercícios
Escreva uma função que recebe um u8 e retorna o resultado de wrapping_add com 200. Teste com 100 e 200.
✓ Resposta:fn wrapping_add_example(x: u8) -> u8 { x.wrapping_add(200) } fn main() { println!("{}", wrapping_add_example(100)); // 44 (300 - 256) println!("{}", wrapping_add_example(200)); // 144 (400 - 256) }Crie uma função que calcula a média de dois números f64 e retorna o resultado. Teste com 3.5 e 4.5.
✓ Resposta:fn media(a: f64, b: f64) -> f64 { (a + b) / 2.0 } fn main() { println!("{}", media(3.5, 4.5)); // 4.0 }Escreva uma expressão que retorne true se um booleano for verdadeiro e um número for maior que 10. Teste com (true, 15) e (false, 5).
✓ Resposta:fn check(b: bool, x: i32) -> bool { b && x > 10 } fn main() { println!("{}", check(true, 15)); // true println!("{}", check(false, 5)); // false }Converta o caractere 'Ω' (Omega maiúsculo, U+03A9) para seu código u32 e depois de volta para char. Imprima ambos.
✓ Resposta:fn main() { let omega: char = 'Ω'; let codigo = omega as u32; println!("Código: {}", codigo); // 937 let de_volta = char::from_u32(codigo).unwrap(); println!("Caractere: {}", de_volta); // Ω }Escreva um programa que some dois números i32, mas use o método checked_add e trate o overflow imprimindo "Overflow!" caso ocorra. Teste com (2147483647, 1) e (100, 200).
✓ Resposta:fn checked_add_example(a: i32, b: i32) { match a.checked_add(b) { Some(soma) => println!("Soma: {}", soma), None => println!("Overflow!") } } fn main() { checked_add_example(2147483647, 1); // Overflow! checked_add_example(100, 200); // Soma: 300 }