Os tipos numéricos são os tipos mais fundamentais em Rust. A linguagem oferece inteiros com e sem sinal em tamanhos de 8 a 128 bits, dois tipos de ponto flutuante, e um conjunto robusto de operações aritméticas que previnem erros comuns como overflow silencioso. Entender esses tipos é essencial para escrever código Rust correto e eficiente.
Tabela de Tipos Inteiros
| Tipo | Tamanho | Faixa de Valores | Uso Típico |
|---|
i8 | 8 bits | -128 a 127 | Dados compactos, C FFI |
i16 | 16 bits | -32.768 a 32.767 | Áudio, C FFI |
i32 | 32 bits | -2.147.483.648 a 2.147.483.647 | Padrão para inteiros |
i64 | 64 bits | -9,2 x 10^18 a 9,2 x 10^18 | Timestamps, IDs grandes |
i128 | 128 bits | -1,7 x 10^38 a 1,7 x 10^38 | Criptografia, cálculos grandes |
u8 | 8 bits | 0 a 255 | Bytes, pixels, ASCII |
u16 | 16 bits | 0 a 65.535 | Portas de rede, Unicode |
u32 | 32 bits | 0 a 4.294.967.295 | Contadores, IDs |
u64 | 64 bits | 0 a 1,8 x 10^19 | Hashes, tamanhos de arquivo |
u128 | 128 bits | 0 a 3,4 x 10^38 | UUIDs, criptografia |
isize | ptr size | Depende da plataforma | Índices de ponteiro |
usize | ptr size | Depende da plataforma | Índices, tamanhos |
Tabela de Tipos de Ponto Flutuante
| Tipo | Tamanho | Precisão | Faixa | Uso Típico |
|---|
f32 | 32 bits | ~7 dígitos | +/-3,4 x 10^38 | Gráficos, GPU, quando memória importa |
f64 | 64 bits | ~15 dígitos | +/-1,8 x 10^308 | Padrão, cálculos científicos |
Literais Numéricos
fn main() {
// Literais inteiros
let decimal = 1_000_000; // separador _ para legibilidade
let hexadecimal = 0xFF; // 255
let octal = 0o77; // 63
let binario = 0b1111_0000; // 240
let byte = b'A'; // u8, valor 65
// Sufixo de tipo
let x = 42i32; // i32
let y = 100u64; // u64
let z = 3.14f32; // f32
// Literais de ponto flutuante
let pi = 3.14159265; // f64 por padrão
let avogadro = 6.022e23; // notação científica
let pequeno = 1.6e-19_f64; // com sufixo
// Tipo padrão
let inteiro = 42; // i32 (padrão)
let flutuante = 3.14; // f64 (padrão)
println!("decimal: {}", decimal);
println!("hex: {:#X}", hexadecimal);
println!("oct: {:#o}", octal);
println!("bin: {:#010b}", binario);
println!("byte: {}", byte);
println!("avogadro: {:.3e}", avogadro);
}
Exemplos Práticos
1. Limites e Constantes dos Tipos
fn main() {
// Constantes MIN e MAX
println!("i8: {} a {}", i8::MIN, i8::MAX);
println!("i16: {} a {}", i16::MIN, i16::MAX);
println!("i32: {} a {}", i32::MIN, i32::MAX);
println!("i64: {} a {}", i64::MIN, i64::MAX);
println!("i128: {} a {}", i128::MIN, i128::MAX);
println!();
println!("u8: {} a {}", u8::MIN, u8::MAX);
println!("u16: {} a {}", u16::MIN, u16::MAX);
println!("u32: {} a {}", u32::MIN, u32::MAX);
println!("u64: {} a {}", u64::MIN, u64::MAX);
println!();
println!("usize: {} a {} ({} bits)", usize::MIN, usize::MAX, usize::BITS);
println!("isize: {} a {} ({} bits)", isize::MIN, isize::MAX, isize::BITS);
println!();
println!("f32 epsilon: {}", f32::EPSILON);
println!("f64 epsilon: {}", f64::EPSILON);
println!("f64 infinity: {}", f64::INFINITY);
println!("f64 NaN: {}", f64::NAN);
println!("f64 pi: {}", std::f64::consts::PI);
println!("f64 e: {}", std::f64::consts::E);
// Tamanho em bytes
println!("\nTamanhos:");
println!(" i32: {} bytes", std::mem::size_of::<i32>());
println!(" u64: {} bytes", std::mem::size_of::<u64>());
println!(" f64: {} bytes", std::mem::size_of::<f64>());
println!(" usize: {} bytes", std::mem::size_of::<usize>());
}
2. Aritmética Segura — checked, wrapping, saturating, overflowing
fn main() {
let a: u8 = 250;
let b: u8 = 10;
// Em modo debug, overflow causa panic!
// let resultado = a + b; // PANIC em debug!
// checked: retorna None em overflow
let checked = a.checked_add(b);
println!("checked_add: {:?}", checked); // None
let checked_ok = 100u8.checked_add(50);
println!("checked_add (ok): {:?}", checked_ok); // Some(150)
// wrapping: faz wrap around (como C)
let wrapped = a.wrapping_add(b);
println!("wrapping_add: {}", wrapped); // 4 (256 + 4 = 260, mod 256 = 4)
// saturating: para no MAX ou MIN
let saturated = a.saturating_add(b);
println!("saturating_add: {}", saturated); // 255 (u8::MAX)
let saturated_sub = 5u8.saturating_sub(10);
println!("saturating_sub: {}", saturated_sub); // 0 (u8::MIN)
// overflowing: retorna (resultado, overflow?)
let (resultado, overflow) = a.overflowing_add(b);
println!("overflowing_add: {} (overflow: {})", resultado, overflow);
// 4 (overflow: true)
// Multiplicação segura
let grande = 1_000_000_000i32;
match grande.checked_mul(3) {
Some(r) => println!("Multiplicação ok: {}", r), // 3_000_000_000 > i32::MAX
None => println!("Overflow na multiplicação!"),
}
// Potência segura
let base: u64 = 2;
println!("2^10 = {}", base.pow(10)); // 1024
println!("2^63 checked = {:?}", base.checked_pow(63)); // Some(...)
println!("2^64 checked = {:?}", base.checked_pow(64)); // None (overflow)
}
3. Conversão entre Tipos — as, From, TryFrom
use std::convert::TryFrom;
fn main() {
// 'as' — conversão explícita (pode perder dados!)
let grande: i64 = 1_000_000;
let pequeno: i32 = grande as i32; // OK, cabe em i32
println!("i64 -> i32: {}", pequeno);
let muito_grande: i64 = 5_000_000_000;
let truncado: i32 = muito_grande as i32; // PERDA DE DADOS!
println!("5 bilhões as i32: {} (TRUNCADO!)", truncado);
// float -> int trunca a parte decimal
let pi = 3.99;
let inteiro = pi as i32; // 3, não 4!
println!("3.99 as i32: {} (truncado, não arredondado)", inteiro);
// int -> float pode perder precisão
let grande_int: i64 = (1i64 << 53) + 1;
let como_f64 = grande_int as f64;
println!("2^53 + 1 como f64: {} (pode perder precisão)", como_f64);
// From — conversão segura (nunca perde dados)
let x: i32 = 42;
let y: i64 = i64::from(x); // i32 -> i64 sempre cabe
let z: f64 = f64::from(x); // i32 -> f64 sempre cabe
println!("From: i32({}) -> i64({}) -> f64({})", x, y, z);
let byte: u8 = 200;
let maior: u32 = u32::from(byte); // u8 -> u32 sempre cabe
println!("u8({}) -> u32({})", byte, maior);
// TryFrom — conversão que pode falhar
let valor: i64 = 300;
match u8::try_from(valor) {
Ok(v) => println!("i64({}) -> u8({}) ok!", valor, v),
Err(e) => println!("i64({}) -> u8 falhou: {}", valor, e),
}
let ok: i64 = 100;
let resultado = u8::try_from(ok);
println!("i64({}) -> u8 = {:?}", ok, resultado); // Ok(100)
// Conversão de string para número
let numero: i32 = "42".parse().unwrap();
let flutuante: f64 = "3.14".parse().unwrap();
println!("parse: {} e {}", numero, flutuante);
// Conversão com tratamento de erro
match "abc".parse::<i32>() {
Ok(n) => println!("Convertido: {}", n),
Err(e) => println!("Erro ao converter 'abc': {}", e),
}
}
4. Métodos Úteis dos Tipos Numéricos
fn main() {
// Métodos de inteiros
let n: i32 = -42;
println!("abs: {}", n.abs()); // 42
println!("signum: {}", n.signum()); // -1
println!("pow: {}", 2i32.pow(10)); // 1024
println!("min: {}", 10i32.min(20)); // 10
println!("max: {}", 10i32.max(20)); // 20
println!("clamp: {}", 150i32.clamp(0, 100)); // 100
// Contagem de bits
let bits: u32 = 0b1010_1100;
println!("\nBits de {:08b}:", bits);
println!(" count_ones: {}", bits.count_ones()); // 4
println!(" count_zeros: {}", bits.count_zeros()); // 28
println!(" leading_zeros: {}", bits.leading_zeros()); // 24
println!(" trailing_zeros: {}", bits.trailing_zeros()); // 2
// Métodos de ponto flutuante
let x: f64 = 2.7;
println!("\nf64 métodos:");
println!(" floor: {}", x.floor()); // 2.0
println!(" ceil: {}", x.ceil()); // 3.0
println!(" round: {}", x.round()); // 3.0
println!(" trunc: {}", x.trunc()); // 2.0
println!(" fract: {}", x.fract()); // 0.7
println!(" sqrt: {}", x.sqrt()); // 1.643...
println!(" cbrt: {}", x.cbrt()); // 1.392...
println!(" ln: {}", x.ln()); // 0.993...
println!(" log2: {}", x.log2()); // 1.432...
println!(" log10: {}", x.log10()); // 0.431...
println!(" sin: {}", x.sin());
println!(" cos: {}", x.cos());
// Verificações de ponto flutuante
let inf = f64::INFINITY;
let nan = f64::NAN;
println!("\nVerificações:");
println!(" INFINITY.is_infinite: {}", inf.is_infinite()); // true
println!(" INFINITY.is_finite: {}", inf.is_finite()); // false
println!(" NAN.is_nan: {}", nan.is_nan()); // true
println!(" 3.14.is_normal: {}", 3.14f64.is_normal()); // true
println!(" 0.0.is_normal: {}", 0.0f64.is_normal()); // false
// CUIDADO: NaN != NaN
println!(" NaN == NaN: {}", nan == nan); // false!
}
5. Padrões Comuns e Boas Práticas
fn main() {
// Evitar overflow com tipos maiores
let fatorial_10: u64 = (1..=10u64).product();
println!("10! = {}", fatorial_10); // 3628800
// Formatar números
let valor = 1_234_567.89;
println!("Padrão: {}", valor);
println!("2 decimais: {:.2}", valor);
println!("Científico: {:.3e}", valor);
println!("Hexadecimal: {:X}", 255);
println!("Binário: {:08b}", 42);
println!("Octal: {:o}", 255);
println!("Com sinal: {:+}", 42);
println!("Padding: {:>10}", 42);
println!("Zero-pad: {:010}", 42);
// Comparar floats com epsilon
let a = 0.1 + 0.2;
let b = 0.3;
println!("\n0.1 + 0.2 == 0.3? {}", a == b); // false!
println!("0.1 + 0.2 = {:.20}", a);
// Comparação correta com epsilon
let epsilon = 1e-10;
println!("Aproximadamente igual? {}", (a - b).abs() < epsilon); // true
// Usar usize para índices
let dados = vec![10, 20, 30, 40, 50];
let indice: usize = 3; // índices são sempre usize
println!("\ndados[{}] = {}", indice, dados[indice]);
// Converter entre isize/usize cuidadosamente
let tamanho: usize = dados.len();
let diferenca: isize = 2 - tamanho as isize; // pode ser negativo
println!("Diferença: {}", diferenca);
// Prevenir divisão por zero
fn dividir_seguro(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
match dividir_seguro(10.0, 3.0) {
Some(r) => println!("10 / 3 = {:.4}", r),
None => println!("Divisão por zero!"),
}
}
Características de Desempenho
| Operação | Inteiros | Floats |
|---|
| Adição/Subtração | O(1), 1 ciclo | O(1), 1-3 ciclos |
| Multiplicação | O(1), 1-3 ciclos | O(1), 3-5 ciclos |
| Divisão | O(1), 20-40 ciclos | O(1), 10-20 ciclos |
| Comparação | O(1), 1 ciclo | O(1), 1-3 ciclos |
| Conversão int->float | O(1), 3-5 ciclos | - |
| checked_add vs add | +1-2 ciclos overhead | - |
| sqrt/sin/cos/ln | - | O(1), 10-30 ciclos |
Tamanho e alinhamento: Os tipos numéricos são alinhados ao seu próprio tamanho. Um i32 ocupa 4 bytes com alinhamento de 4 bytes. Um i128 ocupa 16 bytes com alinhamento de 16 bytes em muitas plataformas.
Escolha de tipo:
- Use
i32 como padrão para inteiros. - Use
u8 para bytes e dados binários. - Use
usize para índices e tamanhos. - Use
f64 como padrão para ponto flutuante. - Use
f32 apenas quando memória/bandwidth importa (GPU, grandes arrays).
Tabela Comparativa: Segurança Aritmética
| Método | Overflow Behavior | Tipo de Retorno | Quando Usar |
|---|
+ (operador) | Panic em debug, wrap em release | T | Quando overflow é um bug |
checked_add | Retorna None | Option<T> | Quando overflow é possível |
wrapping_add | Wrap around | T | Hashing, criptografia |
saturating_add | Para no MAX/MIN | T | Áudio, processamento de sinais |
overflowing_add | Wrap + flag | (T, bool) | Quando precisa saber se houve overflow |
Veja Também