Introdução: por que a conversão de números em Rust não é brincadeira?
\nVocê já escreveu código em Python e fez tranquilamente int_var = 3.14 e depois float_var = int_var? Em Python isso funciona porque os tipos são dinâmicos. Mas em Rust, como um relojoeiro rigoroso, cada engrenagem deve ter seu tamanho certo. E se você tentar encaixar um u8 (0–255) em um u16 — é uma coisa, mas se for o contrário — pode ocorrer um "silencioso" truncamento de dados ou pânico.
No Stack Overflow, a pergunta «How do I convert between numeric types safely and idiomatically?» acumulou milhares de visualizações. E não é por acaso: iniciantes frequentemente se perdem entre as, From e TryFrom. Neste artigo, vamos organizar todas as formas de conversão de números em Rust, explicar quando usar cada uma e mostrar exemplos que você pode aplicar imediatamente.
1. Casting simples com as: rápido, mas perigoso
\nA forma mais simples é o operador as. Ele funciona, mas com armadilhas.
let big: u32 = 300;\nlet small: u8 = big as u8; // 300 não cabe em u8!\nprintln!("{}", small); // Saída: 44 (300 mod 256)\n\nO que aconteceu? Rust simplesmente pegou os 8 bits menos significativos do número 300 (0b1_0010_1100) e descartou o restante, deixando 0b0010_1100 = 44. Isso é chamado de truncamento (truncation). Para u8, isso pode ser uma surpresa.
as é seguro apenas ao expandir o tipo (por exemplo, u8 → u16), quando o valor cabe com certeza. Para redução, é uma bomba-relógio.
Quando usar as? Apenas quando você tem absoluta certeza de que o valor não ultrapassará os limites do tipo de destino. Por exemplo, ao trabalhar com máscaras de bits ou protocolos de baixo nível.
2. Forma idiomática: as traits From e Into
\nRust é uma linguagem onde os erros devem ser explícitos. Por isso, a biblioteca padrão fornece as traits From e Into para conversão segura, que nunca falha.
A regra é simples: se a conversão é sempre bem-sucedida (sem perda de dados), a trait é implementada. Por exemplo:
\nlet small: u8 = 42;\nlet big: u16 = small.into(); // Into chama From automaticamente\n// ou\nlet big: u16 = u16::from(small);\nprintln!("{}", big); // 42\n\nAtenção: From é implementado apenas para pares onde a conversão é garantidamente segura. Por exemplo, u8 → u16, i32 → f64, bool → i32. Mas u16 → u8 — não, porque pode haver estouro.
Tente compilar este código:
\nlet big: u16 = 256;\nlet small: u8 = big.into(); // Erro de compilação!\n\nO compilador dirá: "the trait `From
3. Quando a conversão pode falhar: TryFrom e TryInto
\nE se quisermos tentar converter u16 para u8, mas com verificação? Para isso, temos TryFrom e TryInto. Eles retornam um Result, que precisa ser tratado.
use std::convert::TryFrom;\n\nlet big: u16 = 255;\nmatch u8::try_from(big) {\n Ok(small) => println!("Sucesso: {}", small),\n Err(e) => println!("Erro: {}", e),\n}\n// Saída: Sucesso: 255\n\nlet big: u16 = 256;\nmatch u8::try_from(big) {\n Ok(small) => println!("Sucesso: {}", small),\n Err(e) => println!("Erro: {}", e),\n}\n// Saída: Erro: TryFromIntError(())\n\nIsso é Rust idiomático: você trata explicitamente o possível erro. Pode usar unwrap() se tiver 100% de certeza, mas é melhor usar match.